/*---------------------------------------------------------------------------
* Gamedriver Backend.
*
*---------------------------------------------------------------------------
* This module holds the backend loop, which polls the connected sockets
* for new input, passes the text read to the command parser (and thus
* executes the commands), and keeps track of regular house-keeping chores
* like garbage collection, swapping, heart beats and the triggers of
* call outs and mapping compaction.
*
* Timing is implemented using a one-shot 2 second alarm(). When the
* alarm is triggered, the handler sets the variable comm_time_to_call_heart-
* _beat which is monitored by various functions.
*
* One backend cycle begins with starting the alarm(). Then the pending
* heartbeats are evaluated, but no longer until the time runs out. Any
* heartbeat remaining will be evaluated in the next cycle. After the
* heartbeat, the call_outs are evaluated. Callouts have no time limit,
* but are bound by the eval_cost limit. Next, the object list is scanned
* for objects in need of a reset, cleanup or swap. The driver will
* (due objects given) perform at least one of each operation, but only
* as many as it can before the time runs out. Last, player commands are
* retrieved with get_message(). The semantic is so that all players are
* considered once before get_message() checks for a timeout. If a timeout
* is detected, get_message() select()s, but returns immediately with the
* variable time_to_call_heart_beat set, else it selects() in one second
* intervals until either commands come in or the time runs out.
*
* Also in this file (slighly misplaced) are the file handling efuns,
* regreplace.
*
* TODO: Move the file efuns into separate files.
*---------------------------------------------------------------------------
*/
#include "driver.h"
#include "typedefs.h"
#include <stddef.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#if defined(AMIGA) && !defined(__GNUC__)
#include "hosts/amiga/nsignal.h"
#else
#include <signal.h>
#include <sys/times.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <math.h>
#define NO_REF_STRING
#include "backend.h"
#include "actions.h"
#include "array.h"
#include "call_out.h"
#include "closure.h"
#include "comm.h"
#include "ed.h"
#include "exec.h"
#include "filestat.h"
#include "gcollect.h"
#include "heartbeat.h"
#include "interpret.h"
#include "lex.h"
#include "main.h"
#include "mapping.h"
#include "my-alloca.h"
#include "object.h"
#include "otable.h"
#include "random.h"
#include "regexp.h"
#include "rxcache.h"
#include "simulate.h"
#include "smalloc.h"
#include "stdstrings.h"
#include "stralloc.h"
#include "swap.h"
#include "wiz_list.h"
#include "xalloc.h"
#include "../mudlib/sys/driver_hook.h"
#include "../mudlib/sys/debug_message.h"
/*-------------------------------------------------------------------------*/
#define ALARM_TIME 2 /* The granularity of alarm() calls */
/*-------------------------------------------------------------------------*/
mp_int current_time;
/* The current time, updated every heart beat.
* TODO: Should be time_t if it is given that time_t is always a skalar.
*/
Bool time_to_call_heart_beat;
/* True: It's time to call the heart beat. Set by comm1.c when it recognizes
* an alarm(). */
volatile mp_int alarm_called = MY_FALSE;
/* The alarm() handler sets this to TRUE whenever it is called,
* to allow check_alarm() to verify that the alarm is still alive.
*/
volatile Bool comm_time_to_call_heart_beat = MY_FALSE;
/* True: An heart beat alarm() happened. Set from the alarm handler, this
* causes comm.c to set time_to_call_heart_beat.
*/
volatile mp_int total_alarms = 0;
/* The total number of alarm()s so far, incremented from the alarm handler.
*/
uint32 total_player_commands = 0;
/* Total number of player commands so far.
*/
uint num_listed_objs = 0;
/* Number of objects in the object list.
*/
uint num_last_processed = 0;
/* Number of object processed in last process_objects().
*/
long avg_last_processed = 0;
long avg_in_list = 0;
/* Decaying average number of objects processed and objects in the list.
*/
Bool extra_jobs_to_do = MY_FALSE;
/* True: the backend has other things to do in this cycle than just
* parsing commands or calling the heart_beat.
*/
GC_Request gc_request = gcDont;
/* gcDont: No garbage collection is due.
* gcMalloc: The mallocator requested a gc (requires extra_jobs_to_do).
* gcEfun: GC requested by efun (requires extra_jobs_to_do).
*/
/* TODO: all the 'extra jobs to do' should be collected here, in a nice
* TODO:: struct.
*/
Bool mud_is_up = MY_FALSE;
/* True: the driver is finished with the initial processing
* and has entered the main loop. This flag is currently not
* used by the driver, but can be useful for printf()-style debugging.
*/
static double load_av = 0.0;
/* The load average (player commands/second), weighted over the
* last period of time.
*/
static double compile_av = 0.0;
/* The average of compiled lines/second, weighted over the last period
* of time.
*/
static time_t time_last_slow_shut = 0;
/* Time of the last call to slow_shut_down(), to avoid repeated
* calls while the previous ones are still working.
*/
/*-------------------------------------------------------------------------*/
/* --- Forward declarations --- */
static void process_objects(void);
static void update_load_av (void);
/*-------------------------------------------------------------------------*/
void
clear_state (void)
/* Clear the global variables of the virtual machine. This is necessary
* after errors, which return directly to the backend, thus skipping the
* clean-up code in the VM itself.
*
* This routine must only be called from top level, not from inside
* stack machine execution (as the stack will be cleared).
* TODO: This too belongs into interpret.c
*/
{
current_file = NULL;
current_object = NULL;
command_giver = NULL;
current_interactive = NULL;
previous_ob = NULL;
current_prog = NULL;
reset_machine(MY_FALSE); /* Pop down the stack. */
}
/*-------------------------------------------------------------------------*/
#ifdef DEBUG
static void
do_state_check (int minlvl, const char *where)
/* Perform the simplistic interpret::check_state() if the check_state_lvl
* is at least <minlvl>. If an inconsistency is detected, the message
* "<timestamp> Inconsistency <where>" and a trace of the last instructions
* is logged, then the state is cleared.
*
* Be careful that the call to check_state()/clear_state() is
* not too costly, as it is done in every loop.
*/
{
if (check_state_level >= minlvl && check_state() )
{
debug_message("%s Inconsistency %s\n", time_stamp(), where);
printf("%s Inconsistency %s\n", time_stamp(), where);
dump_trace(MY_TRUE, NULL);
#ifdef TRACE_CODE
last_instructions(TOTAL_TRACE_LENGTH, 1, 0);
#endif
clear_state();
}
} /* do_state_check() */
#else
#define do_state_check(minlvl, where)
#endif
/*-------------------------------------------------------------------------*/
void
logon (object_t *ob)
/* Call the logon() lfun in the object <ob>.
*
* current_object is temporarily set to <ob> in order to allow logon()
* to be static (security measure). Doing so is harmless as there is no
* previous_object to consider.
*
* TODO: This should go into simulate.c or comm.c
*/
{
svalue_t *ret;
object_t *save = current_object;
current_object = ob;
ret = apply(STR_LOGON, ob, 0);
if (ret == 0) {
add_message("prog %s:\n", ob->name);
error("Could not find logon() on the player %s\n", ob->name);
}
current_object = save;
} /* logon() */
/*-------------------------------------------------------------------------*/
#if defined(AMIGA) && !defined(__GNUC__)
static void
exit_alarm_timer (void)
/* Clean up the alarm timer on program exit.
* This is set as atexit() function.
*/
{
alarm(0);
}
#endif
/*-------------------------------------------------------------------------*/
static RETSIGTYPE
handle_hup (int sig UNUSED)
/* SIGHUP handler: request a game shutdown.
*/
{
#ifdef __MWERKS__
# pragma unused(sig)
#endif
extra_jobs_to_do = MY_TRUE;
game_is_being_shut_down = MY_TRUE;
#ifndef RETSIGTYPE_VOID
return 0;
#endif
} /* handle_hup() */
/*-------------------------------------------------------------------------*/
static RETSIGTYPE
handle_usr1 (int sig UNUSED)
/* SIGUSR1 handler: request a master update.
*/
{
#ifdef __MWERKS__
# pragma unused(sig)
#endif
extra_jobs_to_do = MY_TRUE;
master_will_be_updated = MY_TRUE;
eval_cost += max_eval_cost >> 3;
(void)signal(SIGUSR1, handle_usr1);
#ifndef RETSIGTYPE_VOID
return 0;
#endif
} /* handle_usr1() */
/*-------------------------------------------------------------------------*/
static RETSIGTYPE
handle_usr2 (int sig UNUSED)
/* SIGUSR2 handler: reopen the debug.log file.
*/
{
#ifdef __MWERKS__
# pragma unused(sig)
#endif
reopen_debug_log = MY_TRUE;
(void)signal(SIGUSR2, handle_usr2);
#ifndef RETSIGTYPE_VOID
return 0;
#endif
} /* handle_usr1() */
/*-------------------------------------------------------------------------*/
static INLINE void
cleanup_stuff (void)
/* Perform a number of clean up operations: replacing programs, freeing
* driver hooks, and removing destructed object.
* They are collected in one function so that they can be easily called from
* various places (backend loop and the process_objects loops); especially
* since it is not advisable to remove destructed objects before replacing
* the programs.
*/
{
/* Reset the VM to avoid dangling references to invalid objects */
clear_state();
/* Replace programs */
if (obj_list_replace)
{
replace_programs();
}
#ifdef USE_FREE_CLOSURE_HOOK
/* Free older driver hooks
*/
free_old_driver_hooks();
#endif
/* Finish up all newly destructed objects.
*/
handle_newly_destructed_objects();
/* Remove all unreferenced destructed objects.
*/
remove_destructed_objects();
} /* cleanup_stuff() */
/*-------------------------------------------------------------------------*/
void
backend (void)
/* The backend loop, the backbone of the driver's operations.
* It only returns when the game has to be shut down.
*/
{
char buff[MAX_TEXT+4];
/* Note that the size of buff[] is determined by MAX_TEXT, which
* is the max size of the network receive buffer. Iow: no
* buffer overruns possible.
*/
/*
* Set up.
*/
prepare_ipc();
(void)signal(SIGHUP, handle_hup);
(void)signal(SIGUSR1, handle_usr1);
(void)signal(SIGUSR2, handle_usr2);
if (!t_flag) {
/* Start the first alarm */
ALARM_HANDLER_FIRST_CALL(catch_alarm);
current_time = get_current_time();
comm_time_to_call_heart_beat = MY_FALSE;
time_to_call_heart_beat = MY_FALSE;
alarm(ALARM_TIME);
}
#if defined(AMIGA) && !defined(__GNUC__)
atexit(exit_alarm_timer);
#endif
mud_is_up = MY_TRUE;
printf("%s LDMud ready for users.\n", time_stamp());
fflush(stdout);
debug_message("%s LDMud ready for users.\n", time_stamp());
toplevel_context.rt.type = ERROR_RECOVERY_BACKEND;
setjmp(toplevel_context.con.text);
/*
* We come here after errors, and have to clear some global variables.
*/
clear_state();
flush_all_player_mess();
/*
* The Loop.
*/
while(1)
{
do_state_check(1, "in main loop");
check_alarm();
RESET_LIMITS;
CLEAR_EVAL_COST;
#ifdef C_ALLOCA
/* Execute pending deallocations */
alloca(0); /* free alloca'd values from deeper levels of nesting */
#endif
/* Replace programs, remove destructed objects, and similar stuff */
cleanup_stuff();
#ifdef DEBUG
if (check_a_lot_ref_counts_flag)
check_a_lot_ref_counts(NULL);
/* after removing destructed objects! */
#endif
#ifdef CHECK_STRINGS
if (check_string_table_flag)
check_string_table();
#endif
/*
* Do the extra jobs, if any.
*/
if (extra_jobs_to_do) {
current_interactive = NULL;
if (game_is_being_shut_down)
{
command_giver = NULL;
current_object = NULL;
return;
}
if (master_will_be_updated) {
deep_destruct(master_ob);
master_will_be_updated = MY_FALSE;
command_giver = NULL;
current_object = &dummy_current_object_for_loads;
callback_master(STR_EXT_RELOAD, 0);
current_object = NULL;
}
if (gc_request != gcDont) {
time_t time_now = time(NULL);
char buf[120];
if (gc_request == gcEfun
|| time_now - time_last_gc >= 60)
{
sprintf(buf, "%s Garbage collection req by %s "
"(slow_shut to do: %d, "
"time since last gc: %ld)\n"
, time_stamp()
, gc_request == gcEfun ? "efun" : "allocator"
, slow_shut_down_to_do
, (long)(time_now - time_last_gc));
write(1, buf, strlen(buf));
command_giver = NULL;
current_object = NULL;
garbage_collection();
}
else
{
sprintf(buf, "%s Garbage collection req by %s refused "
"(slow_shut to do: %d, "
"time since last gc: %ld)\n"
, time_stamp()
, gc_request == gcEfun ? "efun" : "allocator"
, slow_shut_down_to_do
, (long)(time_now - time_last_gc));
write(1, buf, strlen(buf));
reallocate_reserved_areas();
}
gc_request = gcDont;
if (slow_shut_down_to_do)
{
if (time_now - time_last_slow_shut
>= slow_shut_down_to_do * 60
)
{
int minutes = slow_shut_down_to_do;
char shut_msg[90];
slow_shut_down_to_do = 0;
time_last_slow_shut = time_now;
malloc_privilege = MALLOC_MASTER;
sprintf(shut_msg, "%s slow_shut_down(%d)\n", time_stamp(), minutes);
write(1, shut_msg, strlen(shut_msg));
previous_ob = NULL;
command_giver = NULL;
current_interactive = NULL;
push_number(minutes);
callback_master(STR_SLOW_SHUT, 1);
}
else
{
sprintf(buf, "%s Last slow_shut_down() still pending.\n"
, time_stamp()
);
write(1, buf, strlen(buf));
}
}
malloc_privilege = MALLOC_USER;
}
extra_jobs_to_do = MY_FALSE;
if (num_dirty_mappings) {
compact_mappings((num_dirty_mappings+80) >> 5);
malloc_privilege = MALLOC_USER;
}
} /* if (extra_jobs_to_do */
do_state_check(2, "before get_message()");
/*
* Call comm.c and wait for player input, or until the next
* heart beat is due.
*/
if (get_message(buff))
{
interactive_t *ip;
/* Create the new time_stamp string in the function's local
* buffer.
*/
(void)time_stamp();
total_player_commands++;
update_load_av();
/*
* Now we have a string from the player. This string can go to
* one of several places. If it is prepended with a '!', then
* it is an escape from the 'ed' editor, so we send it
* as a command to the parser.
* If any object function is waiting for an input string, then
* send it there.
* Otherwise, send the string to the parser.
* The command_parser will find that current_object is 0, and
* then set current_object to point to the object that defines
* the command. This will enable such functions to be static.
*/
current_object = NULL;
current_interactive = command_giver;
(void)O_SET_INTERACTIVE(ip, command_giver);
#ifdef DEBUG
if (!ip)
{
fatal("Non interactive player in main loop !\n");
/* NOTREACHED */
}
#endif
ip->set_input_to = MY_FALSE;
tracedepth = 0;
if (buff[0] == '!'
&& buff[1] != '\0'
&& command_giver->super)
{
if(!call_function_interactive(ip, buff)) {
/* We got a bang-input, but no input context wants
* to handle it - treat it as a normal command.
*/
if (ip->noecho & NOECHO)
{
/* !message while in NOECHO - simulate the
* echo by sending the (remaining) raw data we got.
*/
add_message("%s\n", buff + ip->chars_ready);
ip->chars_ready = 0;
}
execute_command(buff+1, command_giver);
}
}
else if (O_GET_EDBUFFER(command_giver))
ed_cmd(buff);
else if (
call_function_interactive(ip, buff))
NOOP;
else
execute_command(buff, command_giver);
/* ip might be invalid again here */
/*
* Print a prompt if player is still here.
*/
if (command_giver)
{
if (O_SET_INTERACTIVE(ip, command_giver)
&& !ip->do_close)
{
print_prompt();
}
}
do_state_check(2, "after handling message");
}
else
{
/* No new message, just reate the new time_stamp string in
* the function's local buffer.
*/
(void)time_stamp();
} /* if (get_message()) */
/* Do the periodic functions if it's time.
*/
if (time_to_call_heart_beat)
{
object_t *hide_current = current_object;
/* TODO: Is there any point to this? */
current_time = get_current_time();
current_object = NULL;
/* Start the next alarm */
comm_time_to_call_heart_beat = MY_FALSE;
time_to_call_heart_beat = MY_FALSE;
alarm(ALARM_TIME);
/* Do the timed events */
do_state_check(2, "before heartbeat");
call_heart_beat();
do_state_check(2, "after heartbeat");
call_out();
do_state_check(2, "after call_out");
/* Reset/cleanup/swap objects.
* TODO: Reset and cleanup/swap should be separated.
*/
process_objects();
do_state_check(2, "after swap/cleanup/reset");
command_giver = NULL;
trace_level = 0;
current_object = hide_current;
wiz_decay();
comm_cleanup_interactives();
}
} /* end of main loop */
/* NOTREACHED */
}
/*-------------------------------------------------------------------------*/
/*
* catch alarm()
*
* Set flag for comms code and heart_beat to catch.
* comms code then sets time_to_call_heart_beat for the backend when
* it has completed the current round of player commands.
*/
#ifdef ALARM_HANDLER
ALARM_HANDLER(catch_alarm, alarm_called = MY_TRUE; comm_time_to_call_heart_beat = MY_TRUE; total_alarms++; )
#else
RETSIGTYPE
catch_alarm (int dummy UNUSED)
{
#ifdef __MWERKS__
# pragma unused(dummy)
#endif
(void)signal(SIGALRM, (RETSIGTYPE(*)(int))catch_alarm);
alarm_called = MY_TRUE;
comm_time_to_call_heart_beat = MY_TRUE;
total_alarms++;
#ifndef RETSIGTYPE_VOID
return 0;
#endif
}
#endif
/*-------------------------------------------------------------------------*/
void check_alarm (void)
/* Check the time since the last recorded call to the alarm handler.
* If it is longer than a limit, assume that the alarm died and restart it.
*
* This function is necessary especially for Cygwin on Windows, where it
* is not unusual that the driver process receives so few cycles that it
* loses its alarm.
* TODO: It should be possible to get rid of alarms altogether if all
* TODO:: timebound methods check the time since the last checkpoint.
*/
{
static mp_int last_alarm_time = 0;
mp_int curtime = get_current_time();
if (t_flag) /* Timing turned off? */
return;
if (!last_alarm_time) /* initialize it */
last_alarm_time = curtime;
if (alarm_called)
{
/* We got an alarm - restart the timer */
last_alarm_time = curtime;
}
else if (curtime - last_alarm_time > 15)
{
debug_message("%s Last alarm was %ld seconds ago - restarting it.\n"
, time_stamp(), curtime - last_alarm_time);
alarm(0); /* stop alarm in case it is still alive, but just slow */
comm_time_to_call_heart_beat = MY_TRUE;
time_to_call_heart_beat = MY_TRUE;
(void)signal(SIGALRM, (RETSIGTYPE(*)(int))catch_alarm);
alarm(ALARM_TIME);
last_alarm_time = curtime; /* Since we just restarted it */
}
alarm_called = MY_FALSE;
} /* check_alarm() */
/*-------------------------------------------------------------------------*/
static void
process_objects (void)
/* This function performs almost all of the less important tasks in the
* mud: resetting, cleaning and swapping objects.
*
* It is called from the backend loop in every new cycle before
* player commands are retrieved, but after heartbeats and callouts
* have been evaluated. It tries to process as many objects as possible
* before the current timeslot runs out (as registered by comm_time_to-
* _call_heart_beat), but will do at least one cleanup/swap and reset.
*
* Objects are moved to the end of the list before they are processed,
* so that a the next object to handle is always the first object
* in the list. Even arbitrary object destruction doesn't change this.
*
* The functions in detail:
*
* - An object will be reset if it is found in a state of not being reset
* and the delay to the next reset has passed.
*
* - An object's clean_up() lfun will be called if the object has not
* been used for quite some time, and if it was not reset in this
* very round.
*
* - An object's program and/or its variables will be swapped if it exists
* for at least time_to_swap(_variables) seconds since the last reference.
* Since swapping of variables is costly, care is taken that variables
* are not swapped out right before the next reset which in that case
* would cause a swap in/swap out-yoyo. Instead, such variable swapping
* is delayed until after the reset occured.
*
* To disable swapping, set the swapping times (either in config.h or per
* commandline option) to a value <= 0.
*
* The function maintains its own error recovery info so that errors
* in reset() or clean_up() won't mess up the handling.
*
* TODO: The objlist should be split into resets and cleanup/swaps, which
* TODO:: are then sorted by due times. This would make this function
* TODO:: much more efficient.
* TODO: It might be a good idea to distinguish between the time_of_ref
* TODO:: (when the object was last used/called) and the time_of_swap,
* TODO:: when it was last swapped in or out. Then, maybe not.
*/
{
static Bool did_reset;
static Bool did_swap;
/* True if one reset call or cleanup/swap was performed.
* static so that errors won't clobber it.
*/
object_t *obj; /* Current object worked on */
struct error_recovery_info error_recovery_info;
/* Local error recovery info */
/* Housekeeping */
num_last_processed = 0;
did_reset = MY_FALSE;
did_swap = MY_FALSE;
error_recovery_info.rt.last = rt_context;
error_recovery_info.rt.type = ERROR_RECOVERY_BACKEND;
rt_context = (rt_context_t *)&error_recovery_info;
if (setjmp(error_recovery_info.con.text))
{
clear_state();
debug_message("%s Error in process_objects().\n", time_stamp());
}
/* The processing loop, runs until either time or objects
* run short.
*
* To be safe against errors and destruction of objects,
* the object processed is moved immediately to the end
* of the object list, so that obj_list always points to
* the next object to process.
*
* The loop ends (apart from timeouts) when num_last_processed
* exceeds num_listed_objs.
*/
while (NULL != (obj = obj_list)
&& (!did_reset || !did_swap || !comm_time_to_call_heart_beat)
&& num_last_processed < num_listed_objs
)
{
mp_int time_since_ref; /* Time since last reference */
mp_int min_time_to_swap; /* Variable swap exclusion time before reset */
Bool bResetCalled; /* TRUE: reset() called */
num_last_processed++;
/* Move obj to the end of the list */
if (obj != obj_list_end)
{
obj_list = obj->next_all;
obj_list->prev_all = NULL;
obj->next_all = NULL;
obj->prev_all = obj_list_end;
obj_list_end->next_all = obj;
obj_list_end = obj;
}
/* Set Variables */
time_since_ref = current_time - obj->time_of_ref;
bResetCalled = MY_FALSE;
/* Variables won't be swapped if a reset is due shortly.
* "shortly" means half the var swap interval, but at max 5 minutes.
*/
min_time_to_swap = 5 * 60;
if (time_to_swap_variables / 2 < min_time_to_swap)
min_time_to_swap = time_to_swap_variables/2;
/* ------ Reset ------ */
/* Check if a reset() is due. Objects which have not been touched
* since the last reset just get a new due time set.
* It is tempting to skip the reset handling for objects which
* are swapped out, but then swapper would have to call reset_object()
* for due objects on swap-in (just setting a new due-time is not
* sufficient).
* TODO: Do exactly that?
*/
if (time_to_reset > 0
&& obj->time_reset
&& obj->time_reset < current_time)
{
if (obj->flags & O_RESET_STATE)
{
#ifdef DEBUG
if (d_flag)
fprintf(stderr, "%s RESET (virtual) %s\n", time_stamp(), obj->name);
#endif
obj->time_reset = current_time+time_to_reset/2
+(mp_int)random_number((uint32)time_to_reset/2);
}
else if (!did_reset || !comm_time_to_call_heart_beat)
{
#ifdef DEBUG
if (d_flag)
fprintf(stderr, "%s RESET %s\n", time_stamp(), obj->name);
#endif
if (obj->flags & O_SWAPPED
&& load_ob_from_swap(obj) < 0)
continue;
bResetCalled = MY_TRUE;
did_reset = MY_TRUE;
RESET_LIMITS;
CLEAR_EVAL_COST;
command_giver = 0;
previous_ob = NULL;
trace_level = 0;
reset_object(obj, H_RESET);
if (obj->flags & O_DESTRUCTED)
continue;
if (time_to_swap > 0 || time_to_swap_variables > 0)
{
/* Restore old time_of_ref. This might result in a quick
* swap-in/swap-out yoyo if this object was swapped out
* in the first place. To make this less costly, variables
* are not swapped out short before a reset (see below).
*/
obj->time_of_ref = current_time - time_since_ref;
}
} /* if (call reset or not) */
} /* if (needs reset?) */
/* ------ Clean Up ------ */
/* If enough time has passed, give the object a chance to self-
* destruct. The O_RESET_STATE is saved over the call to clean_up().
*
* Only call clean_up in objects that have defined such a function.
* Only if the clean_up returns a non-zero value, it will be called
* again.
*
* The cleanup is not called if the object was actively reset
* just before.
*/
if (time_to_cleanup > 0
&& obj->flags & O_WILL_CLEAN_UP
&& time_since_ref > time_to_cleanup
&& !bResetCalled
&& (!did_swap || !comm_time_to_call_heart_beat))
{
int save_reset_state = obj->flags & O_RESET_STATE;
svalue_t *svp;
#ifdef DEBUG
if (d_flag)
fprintf(stderr, "%s CLEANUP %s\n", time_stamp(), obj->name);
#endif
did_swap = MY_TRUE;
/* Remove all pending destructed objects, to get a true refcount.
* But make sure that we don't clobber anything else while
* doing so.
*/
cleanup_stuff();
/* Supply a flag to the object that says if this program
* is inherited by other objects. Cloned objects might as well
* believe they are not inherited. Swapped objects will not
* have a ref count > 1 (and will have an invalid ob->prog
* pointer). If the object is a blueprint, the extra reference
* from the program will not be counted.
*/
if (obj->flags & (O_CLONE|O_REPLACED))
push_number(0);
else if (O_PROG_SWAPPED(obj))
push_number(1);
else if (obj->prog->blueprint == obj)
push_number(obj->prog->ref - 1);
else
push_number(obj->prog->ref);
RESET_LIMITS;
CLEAR_EVAL_COST;
command_giver = NULL;
previous_ob = NULL;
trace_level = 0;
if (driver_hook[H_CLEAN_UP].type == T_CLOSURE)
{
lambda_t *l;
l = driver_hook[H_CLEAN_UP].u.lambda;
if (driver_hook[H_CLEAN_UP].x.closure_type == CLOSURE_LAMBDA)
l->ob = obj;
push_object(obj);
call_lambda(&driver_hook[H_CLEAN_UP], 2);
svp = inter_sp;
pop_stack();
}
else if (driver_hook[H_CLEAN_UP].type == T_STRING)
{
svp = apply(driver_hook[H_CLEAN_UP].u.string, obj, 1);
}
else
{
pop_stack();
goto no_clean_up;
}
if (obj->flags & O_DESTRUCTED)
continue;
if (!svp
|| (svp->type == T_NUMBER && svp->u.number == 0)
)
obj->flags &= ~O_WILL_CLEAN_UP;
obj->flags |= save_reset_state;
no_clean_up:
obj->time_of_ref = current_time;
/* in case the hook didn't update it */
}
/* ------ Swapping ------ */
/* At last, there is a possibility that the object can be swapped
* out.
*
* Variables are swapped after time_to_swap_variables has elapsed
* since the last ref, and if the object is either still reset or
* the next reset is at least min(5 minutes, time_to_swap_variables/2)
* in the future. When a reset is due, this second condition delays the
* costly variable swapping until after the reset.
*
* Programs are swapped after time_to_swap has elapsed, and if
* they have only one reference, ie are not cloned or inherited.
* Since program swapping is relatively cheap, no care is
* taken of resets.
*/
if ( (time_to_swap > 0 || time_to_swap_variables > 0)
&& !(obj->flags & O_HEART_BEAT)
&& (!did_swap || !comm_time_to_call_heart_beat))
{
/* Swap the variables, if possible */
if (!O_VAR_SWAPPED(obj)
&& time_since_ref >= time_to_swap_variables
&& time_to_swap_variables > 0
&& obj->variables
&& ( obj->flags & O_RESET_STATE
|| !obj->time_reset
|| (obj->time_reset - current_time > min_time_to_swap)))
{
#ifdef DEBUG
if (d_flag)
fprintf(stderr, "%s swap vars of %s\n", time_stamp(), obj->name);
#endif
swap_variables(obj);
if (O_VAR_SWAPPED(obj))
did_swap = MY_TRUE;
}
/* Swap the program, if possible */
if (!O_PROG_SWAPPED(obj)
&& obj->prog->ref == 1
&& time_since_ref >= time_to_swap
&& time_to_swap > 0)
{
#ifdef DEBUG
if (d_flag)
fprintf(stderr, "%s swap %s\n", time_stamp(), obj->name);
#endif
swap_program(obj);
if (O_PROG_SWAPPED(obj))
did_swap = MY_TRUE;
}
} /* if (obj can be swapped) */
/* TODO: Here would be nice place to convert all strings in an
* TODO:: object to shared strings, if the object was reset, cleant
* TODO:: up, or not used for some time. Also, all the destructed
* TODO:: object references in the object could be cleared.
* TODO:: The function (in simulate) could have the signature:
* TODO:: void clean_vars(object *ob, pointer_table *ptable).
*/
} /* End of loop */
/* Update the processing averages
*/
avg_last_processed += num_last_processed - (avg_last_processed >> 10);
avg_in_list += num_listed_objs - (avg_in_list >> 10);
/* Restore the error recovery context */
rt_context = error_recovery_info.rt.last;
}
/*-------------------------------------------------------------------------*/
void
preload_objects (int eflag)
/* Perform some initialisation of the mudlib before the sockets are opened
* for users. Traditionally, back in 2.4.5 times, this was used to preload
* the wizard castles, but you can do everything else here.
*
* <eflag> is the number of times the '-e' ('--no-preload') option was
* given on the command line. Traditionally, a non-zero value disabled
* any preloading.
*
* The preloading is a two-step process. First, the lfun epilog() in the
* master object is called with the value of <eflag> as argument. The
* result is expected to be an array of strings (filenames). These strings
* (or at least those array elements which are strings) are then fed as
* argument to the master object lfun preload().
*/
{
vector_t *prefiles;
svalue_t *ret;
static mp_int ix0;
static size_t num_prefiles;
mp_int ix;
/* Call master->epilog(<eflag>)
*/
push_number(eflag);
ret = callback_master(STR_EPILOG, 1);
if ((ret == 0) || (ret->type != T_POINTER))
return;
else
prefiles = ret->u.vec;
if ((prefiles == 0) || ((num_prefiles = VEC_SIZE(prefiles)) < 1))
return;
ref_array(prefiles);
/* Without this, the next apply call would free the array prefiles.
*/
ix0 = -1;
/* In case of errors, return here and simply continue with
* the loop.
*/
toplevel_context.rt.type = ERROR_RECOVERY_BACKEND;
if (setjmp(toplevel_context.con.text)) {
clear_state();
add_message("Anomaly in the fabric of world space.\n");
}
/* Loop through the prefiles array, calling master->preload()
* for every string in it.
*/
while ((ix = ++ix0) < num_prefiles) {
if (prefiles->item[ix].type != T_STRING)
continue;
RESET_LIMITS;
CLEAR_EVAL_COST;
push_string_malloced(prefiles->item[ix].u.string);
(void)apply_master(STR_PRELOAD, 1);
}
free_array(prefiles);
toplevel_context.rt.type = ERROR_RECOVERY_NONE;
}
/*-------------------------------------------------------------------------*/
static void
update_load_av (void)
/* Compute the load average (player commands/second), weighted over the
* last period of time.
*
* The function is called after every player command and basically counts
* how many times it is called in one backend loop.
*/
{
static int last_time; /* Time of last backend loop */
static int acc = 0; /* Number of calls in this backend loop */
int n;
double c;
acc++;
if (current_time == last_time)
return;
n = current_time - last_time;
if (n < (int) (sizeof consts / sizeof consts[0]) )
c = consts[n];
else
c = exp(- n / 900.0);
load_av = c * load_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
/*-------------------------------------------------------------------------*/
void
update_compile_av (int lines)
/* Compute the average of compiled lines/second, weighted over the
* last period of time.
*
* The function is called after every compilation and basically sums up
* the number of compiled lines in one backend loop.
*/
{
static int last_time; /* Time of the last backend loop */
static int acc = 0; /* Sum of lines for this backend loop */
int n;
double c;
acc += lines;
if (current_time == last_time)
return;
n = current_time - last_time;
if (n < (int) (sizeof consts / sizeof consts[0]) )
c = consts[n];
else
c = exp(- n / 900.0);
compile_av = c * compile_av + acc * (1 - c) / n;
last_time = current_time;
acc = 0;
}
/*=========================================================================*/
/* EFUNS */
/*-------------------------------------------------------------------------*/
char *
query_load_av (void)
/* EFUN query_load_average()
*
* Return a string with the current load_av and compile_av.
* The string returned points to a local static buffer.
*/
{
static char buff[100];
#if defined(__MWERKS__) && !defined(WARN_ALL)
# pragma warn_largeargs off
#endif
sprintf(buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av);
#if defined(__MWERKS__)
# pragma warn_largeargs reset
#endif
return buff;
}
/*-------------------------------------------------------------------------*/
svalue_t *
f_debug_message (svalue_t *sp)
/* TEFUN debug_message()
*
* debug_message(string text)
* debug_message(string text, int flags)
*
* Print the <text> to stdout, stderr, and/or the <host>.debug.log file.
*
* The parameter <flags> is a combination of bitflags determining the
* target and the mode of writing.
*
* The target flags are: DMSG_STDOUT, DMSG_STDERR and DMSG_LOGFILE.
* If the flag DMSG_STAMP is given, the message is prepended with the
* current date and time in the format 'YYYY.MM.DD HH:MM:SS '.
*
* If <flags> is given as 0, left out, or contains no target
* definition, debug_message() will print to stdout and to the logfile.
*/
{
if ((sp-1)->type != T_STRING)
bad_xefun_arg(1, sp);
if (sp->type != T_NUMBER
|| (sp->u.number & ~(DMSG_STDOUT|DMSG_STDERR|DMSG_LOGFILE|DMSG_STAMP)) != 0)
bad_xefun_arg(2, sp);
if (!(sp->u.number & DMSG_TARGET) || (sp->u.number & DMSG_STDOUT))
{
if (sp->u.number & DMSG_STAMP)
printf("%s %s", time_stamp(), (sp-1)->u.string);
else
printf("%s", (sp-1)->u.string);
}
if (sp->u.number & DMSG_STDERR)
{
if (sp->u.number & DMSG_STAMP)
fprintf(stderr, "%s %s", time_stamp(), (sp-1)->u.string);
else
fprintf(stderr, "%s", (sp-1)->u.string);
}
if (!(sp->u.number & DMSG_TARGET) || (sp->u.number & DMSG_LOGFILE))
{
if (sp->u.number & DMSG_STAMP)
debug_message("%s %s", time_stamp(), (sp-1)->u.string);
else
debug_message("%s", (sp-1)->u.string);
}
free_svalue(sp);
free_svalue(sp-1);
return sp - 2;
}
/*-------------------------------------------------------------------------*/
int
e_write_file (char *file, char *str)
/* EFUN write_file()
*
* Append the text <str> to the end of <file>
* Return 0 for failure, otherwise 1.
*
* Note that this function might be called recursively (see below).
*/
{
FILE *f;
file = check_valid_path(file, current_object, "write_file", MY_TRUE);
if (!file)
return 0;
f = fopen(file, "a");
if (f == NULL) {
if ((errno == EMFILE
#ifdef ENFILE
|| errno == ENFILE
) && current_file
#endif
) {
/* We are called from within the compiler, probably to write
* an error message into a log.
* Call lex_close() (-> lexerror() -> yyerror() -> parse_error()
* -> apply_master_ob() ) to try to close some files, the try
* again.
*/
push_apply_value();
lex_close(NULL);
pop_apply_value();
f = fopen(file, "a");
}
if (f == NULL) {
char * emsg;
int err = errno;
emsg = strerror(err);
if (emsg)
{
error("Could not open %s for append: (%d) %s.\n"
, file, err, emsg);
}
else
{
perror("write_file");
error("Could not open %s for append: errno %d.\n"
, file, err);
}
/* NOTREACHED */
}
}
FCOUNT_WRITE(file);
fwrite(str, strlen(str), 1, f);
fclose(f);
return 1;
} /* e_write_file() */
/*-------------------------------------------------------------------------*/
char *
e_read_file (char *file, int start, int len)
/* EFUN read_file()
*
* Read <len> lines from <file>, starting with line <start> (counting
* up from 1). If <len> is 0, the whole file is read.
*
* Result is a pointer to a buffer with the read text, or NULL
* on failures. The single lines of the text read are always
* terminated with a single '\n'.
*
* When <start> or <len> are given, the function returns up max_file_xfer
* bytes of text. If the whole file should be read, but is greater than
* the above limit, or if not all of <len> lines fit into the buffer,
* NULL is returned.
*
* TODO: What does <len> == -1 do?
*/
{
struct stat st;
FILE *f;
char *str, *p, *p2, *end, c;
long size; /* TODO: fpos_t? */
if (len < 0 && len != -1)
return NULL;
file = check_valid_path(file, current_object, "read_file", MY_FALSE);
if (!file)
return NULL;
/* If the file would be opened in text mode, the size from fstat would
* not match the number of characters that we can read.
*/
f = fopen(file, "rb");
if (f == NULL)
return NULL;
FCOUNT_READ(file);
/* Check if the file is small enough to be read. */
if (fstat(fileno(f), &st) == -1)
{
fatal("Could not stat an open file.\n");
/* NOTREACHED */
return NULL;
}
size = (long)st.st_size;
if (max_file_xfer && size > max_file_xfer)
{
if ( start || len )
size = max_file_xfer;
else {
fclose(f);
return NULL;
}
}
/* Make the arguments sane */
if (!start) start = 1;
if (!len) len = size;
/* Get the memory */
str = xalloc((size_t)size + 2); /* allow a trailing \0 and leading ' ' */
if (!str) {
fclose(f);
error("(read_file) Out of memory (%ld bytes) for buffer\n", size+2);
/* NOTREACHED */
return NULL;
}
*str++ = ' '; /* this way, we can always read the 'previous' char... */
str[size] = '\0';
/* Search for the first line to read.
* For this, the file is read in chunks of <size> bytes, st.st_size
* records the remaining length of the file.
*/
do {
/* Read the next chunk */
if (size > st.st_size) /* Happens with the last block */
size = (long)st.st_size;
if ((!size && start > 1) || fread(str, (size_t)size, 1, f) != 1) {
fclose(f);
xfree(str-1);
return NULL;
}
st.st_size -= size;
end = str+size;
/* Find all the '\n' in the chunk and count them */
for (p = str; NULL != ( p2 = memchr(p, '\n', (size_t)(end-p)) ) && --start; )
p = p2+1;
} while ( start > 1 );
/* p now points to the first requested line.
* st.st_size is the remaining size of the file.
*/
/* Shift the found lines back to the front of the buffer, and
* count them.
* Also convert \r\n pairs into \n on MS-DOS filesystems.
*/
for (p2 = str; p != end; ) {
c = *p++;
if ( c == '\n' ) {
#ifdef MSDOS_FS
if ( p2[-1] == '\r' ) p2--;
#endif
if (!--len) {
*p2++=c;
break;
}
}
*p2++ = c;
}
/* If there are still some lines missing, and parts of the file
* are not read yet, read and scan those remaining parts.
*/
if ( len && st.st_size ) {
/* Read the remaining file, but only as much as there is
* space left in the buffer. As that one is max_file_xfer
* long, it has to be sufficient.
*/
size -= ( p2-str) ;
if (size > st.st_size)
size = (long)st.st_size;
if (fread(p2, (size_t)size, 1, f) != 1) {
fclose(f);
xfree(str-1);
return NULL;
}
st.st_size -= size;
end = p2+size;
/* Count the remaining lines, again converting \r\n into \n
* when necessary.
*/
for (p = p2; p != end; ) {
c = *p++;
if ( c == '\n' ) {
#ifdef MSDOS_FS
if ( p2[-1] == '\r' ) p2--;
#endif
if (!--len) {
*p2++ = c;
break;
}
}
*p2++ = c;
}
/* If there are lines missing and the file is not at its end,
* we have a failure.
*/
if ( st.st_size && len > 0) {
/* tried to read more than READ_MAX_FILE_SIZE */
fclose(f);
xfree(str-1);
return NULL;
}
}
*p2 = '\0';
fclose(f);
/* Make a copy of the valid parts of the str buffer, then
* get rid of the largish buffer itself.
*/
p2 = string_copy(str); /* TODO: string_n_copy() */
xfree(str-1);
if (!p2)
error("(read_file) Out of memory for result\n");
return p2;
} /* e_read_file() */
/*-------------------------------------------------------------------------*/
char *
e_read_bytes (char *file, int start, int len)
/* EFUN read_bytes()
*
* Read <len> bytes (but mostly max_byte_xfer) from <file>, starting
* with byte <start> (counting up from 0). If <start> is negative, it is
* counted from the end of the file. If <len> is 0, an empty (but valid)
* string is returned.
*
* Result is a pointer to a buffer with the read text, or NULL
* on failures.
*/
{
struct stat st;
char *str,*p;
int f;
long size; /* TODO: fpos_t? */
/* Perform some sanity checks */
if (len < 0 || (max_byte_xfer && len > max_byte_xfer))
return NULL;
file = check_valid_path(file, current_object, "read_bytes", MY_FALSE);
if (!file)
return NULL;
/* Open the file and determine its size */
f = ixopen(file, O_RDONLY);
if (f < 0)
return NULL;
FCOUNT_READ(file);
if (fstat(f, &st) == -1)
fatal("Could not stat an open file.\n");
size = (long)st.st_size;
/* Determine the proper start and len to use */
if (start < 0)
start = size + start;
if (start >= size) {
close(f);
return NULL;
}
if ((start+len) > size)
len = (size - start);
/* Seek and read */
if ((size = (long)lseek(f,start, 0)) < 0) {
close(f);
return NULL;
}
str = xalloc((size_t)len + 1);
if (!str) {
close(f);
return NULL;
}
size = read(f, str, (size_t)len);
close(f);
if (size <= 0) {
xfree(str);
return NULL;
}
/* No postprocessing, except for the adding of the '\0' without
* the gamedriver won't be happy.
*/
str[size] = '\0';
/* We return a copy of the life parts of the buffer, and get rid
* of the largish buffer itself.
*/
p = string_copy(str);
xfree(str);
return p;
} /* e_read_bytes() */
/*-------------------------------------------------------------------------*/
int
e_write_bytes (char *file, int start, char *str)
/* EFUN write_bytes()
*
* Write <str> (but not more than max_byte_xfer bytes) to <file>,
* starting with byte <start> (counting up from 0). If <start> is negative,
* it is counted from the end of the file.
*
* Result is 0 on failure, and 1 otherwise.
*/
{
struct stat st;
mp_int size, len;
int f;
/* Sanity checks */
file = check_valid_path(file, current_object, "write_bytes", MY_TRUE);
if (!file)
return 0;
len = (mp_int)strlen(str);
if (max_byte_xfer && len > max_byte_xfer)
return 0;
f = ixopen(file, O_WRONLY);
if (f < 0)
return 0;
FCOUNT_WRITE(file);
if (fstat(f, &st) == -1)
fatal("Could not stat an open file.\n");
size = (mp_int)st.st_size;
if(start < 0)
start = size + start;
if (start > size) {
close(f);
return 0;
}
if ((size = (mp_int)lseek(f,start, 0)) < 0) {
close(f);
return 0;
}
size = write(f, str, (size_t)len);
close(f);
if (size <= 0) {
return 0;
}
return 1;
} /* e_write_bytes() */
/*-------------------------------------------------------------------------*/
long
e_file_size (char *file)
/* EFUN file_size()
*
* Determine the length of <file> and return it.
* Return -1 if the file doesn't exist, -2 if the name points to a directory.
* These values must match the definitions in mudlib/sys/files.h.
*/
{
struct stat st;
st.st_mode = 0; /* Silences ZeroFault/AIX under high optimizations */
file = check_valid_path(file, current_object, "file_size", MY_FALSE);
if (!file)
return -1;
if (ixstat(file, &st) == -1)
return -1;
if (S_IFDIR & st.st_mode)
return -2;
return (long)st.st_size;
} /* e_file_size() */
/*-------------------------------------------------------------------------*/
svalue_t*
f_regreplace (svalue_t *sp)
/* TEFUN regreplace()
*
* string regreplace (string txt, string pattern, closure|string replace
* , int flags)
*
* Search through <txt> for one/all occurences of <pattern> and replace them
* with the <replace> pattern, returning the result.
* <replace> can be a string, or a closure returning a string. If it is
* a closure, it will be called with the matched substring and
* the position at which it was found as arguments.
* <flags> is the bit-or of these values:
* F_GLOBAL = 1: when given, all occurences of <pattern> are replace,
* else just the first
* F_EXCOMPAT = 2: when given, the expressions are ex-compatible,
* else they aren't.
* TODO: The gamedriver should write these values into an include file.
*
* The function behaves like the s/<pattern>/<replace>/<flags> command
* in sed or vi. It offers an efficient and far more powerful replacement
* for implode(regexplode()).
*/
{
#define F_GLOBAL 0x1
#define F_EXCOMPAT 0x2
struct regexp *pat;
int flags;
char *oldbuf, *buf, *curr, *new, *start, *old, *sub, *match;
size_t matchsize = 0;
svalue_t *subclosure = NULL;
long space;
size_t origspace;
/*
* Must set inter_sp before call to REGCOMP,
* because it might call hs_regerror.
*/
inter_sp = sp;
/* Extract the arguments */
if (sp->type != T_NUMBER)
bad_xefun_arg(4, sp);
flags = sp->u.number;
if (sp[-1].type != T_STRING && sp[-1].type != T_CLOSURE)
bad_xefun_arg(3, sp);
if (sp[-1].type == T_STRING)
{
sub = sp[-1].u.string;
subclosure = NULL;
match = NULL;
}
else
{
sub = NULL;
subclosure = sp-1;
match = NULL;
}
if (sp[-2].type != T_STRING)
bad_xefun_arg(2, sp);
if (sp[-3].type != T_STRING)
bad_xefun_arg(1, sp);
start = curr = sp[-3].u.string;
space = (long)(origspace = (strlen(start)+1)*2);
/* reallocate on the fly */
#define XREALLOC \
space += origspace;\
origspace = origspace*2;\
oldbuf = buf;\
buf = (char*)rexalloc(buf,origspace);\
if (!buf) { \
xfree(oldbuf); \
if (pat) REGFREE(pat); \
error("(regreplace) Out of memory (%lu bytes) for buffer\n"\
, (unsigned long)origspace); \
} \
new = buf + (new-oldbuf)
/* The rexalloc() above read originally 'rexalloc(buf, origspace*2)'.
* Marcus inserted the '*2' since he experienced strange driver
* crashes without. I think that the error corrected in dev.28 was the
* real reason for the crashes, so that the safety factor is no longer
* necessary. However, if regreplace() causes crashes again, this
* is one thing to try.
*/
xallocate(buf, (size_t)space, "buffer");
new = buf;
pat = REGCOMP((unsigned char *)(sp[-2].u.string),(flags & F_EXCOMPAT) ? 1 : 0, MY_FALSE);
/* REGCOMP returns NULL on bad regular expressions. */
if (pat && hs_regexec(pat,curr,start)) {
do {
size_t diff = (size_t)(pat->startp[0]-curr);
space -= diff;
while (space <= 0) {
XREALLOC;
}
strncpy(new,curr,(size_t)diff);
new += diff;
old = new;
/* Determine the replacement string.
*/
if (subclosure != NULL)
{
size_t patsize = pat->endp[0] - pat->startp[0];
if (patsize+1 > matchsize)
{
char * nmatch;
matchsize = patsize+1;
if (match)
nmatch = rexalloc(match, matchsize);
else
nmatch = xalloc(matchsize);
if (!nmatch)
{
xfree(buf);
if (pat)
REGFREE(pat);
error("Out of memory for matched string (%lu bytes)\n"
, (unsigned long)patsize+1);
/* NOTREACHED */
return NULL;
}
match = nmatch;
}
memcpy(match, pat->startp[0], patsize);
match[patsize] = '\0';
push_volatile_string(match);
push_number(pat->startp[0] - start);
call_lambda(subclosure, 2);
transfer_svalue(&apply_return_value, inter_sp);
inter_sp--;
if (apply_return_value.type != T_STRING)
{
xfree(buf);
if (pat)
REGFREE(pat);
if (match)
xfree(match);
error("Invalid type for string replacement\n");
/* NOTREACHED */
return NULL;
}
sub = apply_return_value.u.string;
}
/* Now what may have happen here. We *could*
* be out of mem (as in 'space') or it could be
* a regexp problem. the 'space' problem we
* can handle, the regexp problem not.
* hack: we store a \0 into *old ... if it is
* still there on failure, it is a real failure.
* if not, increase space. The player could get
* some irritating messages from hs_regerror()
*/
*old = '\0';
while (NULL == (new = hs_regsub(pat, sub, new, space, 1)) )
{
int xold;
if (!*old)
{
xfree(buf);
if (pat)
REGFREE(pat);
if (match)
xfree(match);
error("Internal error in regreplace().\n");
/* NOTREACHED */
return NULL;
}
xold = old - buf;
XREALLOC;
new = buf + xold;
old = buf + xold;
*old='\0';
}
space -= new - old;
while (space <= 0) {
XREALLOC;
}
if (curr == pat->endp[0])
{
/* prevent infinite loop
* by advancing one character.
*/
if (!*curr) break;
--space;
while (space <= 0) {
XREALLOC;
}
*new++ = *curr++;
}
else
curr = pat->endp[0];
} while ( (flags & F_GLOBAL)
&& !pat->reganch
&& *curr != '\0'
&& hs_regexec(pat,curr,start)
);
space -= strlen(curr)+1;
if (space <= 0) {
XREALLOC;
}
strcpy(new,curr);
}
else
{
/* Pattern not found -> no editing necessary */
strcpy(buf,start);
}
if (match)
xfree(match);
if (pat)
REGFREE(pat);
free_svalue(sp);
sp--;
free_svalue(sp);
sp--;
free_svalue(sp);
sp--;
free_svalue(sp);
put_malloced_string(sp, string_copy(buf));
xfree(buf);
return sp;
#undef F_EXCOMPAT
#undef F_GLOBAL
#undef XREALLOC
}
/***************************************************************************/