/
driver3.2@242/autoconf/
driver3.2@242/doc/LPC/
driver3.2@242/hosts/
driver3.2@242/hosts/amiga/NetIncl/
driver3.2@242/hosts/amiga/NetIncl/netinet/
driver3.2@242/hosts/amiga/NetIncl/sys/
driver3.2@242/hosts/atari/
driver3.2@242/hosts/fcrypt/
driver3.2@242/mudlib/
driver3.2@242/mudlib/sys/
driver3.2@242/util/
driver3.2@242/util/indent/hosts/next/
driver3.2@242/util/make_docs/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef AMIGA
#include "hosts/amiga/nsignal.h"
#else
#include <signal.h>
#include <sys/times.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#ifdef MSDOS
#define CLOCKS_PER_SEC 1000
#endif
#ifndef CLOCKS_PER_SEC
#define CLOCKS_PER_SEC CLK_TCK
#ifndef CLK_TCK
#define CLK_TCK 60
#endif
#endif
#include <math.h>

#include "lint.h"
#include "config.h"
#include "interpret.h"
#include "object.h"
#include "wiz_list.h"
#include "exec.h"
#include "comm.h"

struct error_recovery_info toplevel_error_recovery_info = {
    (struct error_recovery_info*)0,
    ERROR_RECOVERY_NONE
};

struct error_recovery_info *error_recovery_pointer =
	&toplevel_error_recovery_info;
/*
 * The 'current_time' is updated at every heart beat.
 */
int current_time;

static void cycle_hb_list PROT((void));
extern struct object *command_giver, *current_interactive, *obj_list_destruct;
extern int num_player, d_flag;
extern struct object *previous_ob, *master_ob;
extern int trace_level;
extern int tracedepth;


struct object *current_heart_beat;

void call_heart_beat(), catch_alarm();
void load_first_objects(), prepare_ipc(),
    shutdowngame(), ed_cmd PROT((char *)),
    print_prompt(), call_out(),
    destruct2 PROT((struct object *));

extern int get_message PROT((char *, int)), player_parser PROT((char *)),
    call_function_interactive PROT((struct interactive *, char *)),
    resort_free_list(), swap PROT((struct object *));

extern void flush_all_player_mess();

extern int t_flag;
int time_to_call_heart_beat;
int comm_time_to_call_heart_beat = 0; /* this is set by interrupt, */
	/* comm sets time_to_call_heart_beat sometime after */

/*
 * There are global variables that must be zeroed before any execution.
 * In case of errors, there will be a longjmp(), and the variables will
 * have to be cleared explicitely. They are normally maintained by the
 * code that use them.
 *
 * This routine must only be called from top level, not from inside
 * stack machine execution (as stack will be cleared).
 */
void clear_state() {
    extern struct object *previous_ob;
    extern char *current_file;

    current_file = 0;
    current_object = 0;
    command_giver = 0;
    current_interactive = 0;
    previous_ob = 0;
    current_prog = 0;
    reset_machine(0);	/* Pop down the stack. */
}

extern int check_state();

void logon(ob)
    struct object *ob;
{
    struct svalue *ret;
    struct object *save = current_object;

    /*
     * current_object must be set here, so that the static "logon" in
     * player.c can be called.
     */
    current_object = ob;
    ret = apply("logon", ob, 0);
    if (ret == 0) {
	add_message("prog %s:\n", ob->name);
	fatal("Could not find logon on the player %s\n", ob->name);
    }
    current_object = save;
}

/*
 * Take a player command and parse it.
 * The command can also come from a NPC.
 * Beware that 'str' can be modified and extended !
 */
int parse_command(str, ob)
    char *str;
    struct object *ob;
{
    struct object *save = command_giver;
    int res;

    command_giver = ob;
    res = player_parser(str);
    command_giver = check_object(save);
    return res;
}

#ifdef AMIGA
/* Clean up the alarm timer, this is set as atexit() function */

void exit_alarm_timer() { alarm(0); }
#endif

/*
 * This is the backend. We will stay here for ever (almost).
 */
int32 initial_eval_cost = -MAX_COST;
int32 eval_cost, assigned_eval_cost;
int extra_jobs_to_do = 0;
int garbage_collect_to_do = 0;
void backend()
{
    char buff[2000];
    extern int game_is_being_shut_down;
    extern int slow_shut_down_to_do;
    extern int master_will_be_updated;
    extern mp_int num_dirty_mappings;
    extern int malloc_privilege;

    (void)printf("Setting up ipc.\n");
    fflush(stdout);
    prepare_ipc();
    (void)signal(SIGHUP,  (void(*)())startshutdowngame);
    (void)signal(SIGUSR1, (void(*)())startmasterupdate);
    if (!t_flag) {
	catch_alarm();
	call_heart_beat();
    }
#ifdef AMIGA
    atexit(exit_alarm_timer);
#endif
    toplevel_error_recovery_info.type = ERROR_RECOVERY_BACKEND;
    setjmp(toplevel_error_recovery_info.context);
    /*
     * We come here after errors, and have to clear some global variables.
     */
    clear_state();
    flush_all_player_mess();
    while(1) {
	/*
	 * The call of clear_state() should not really have to be done
	 * once every loop. However, there seem to be holes where the
	 * state is not consistent. If these holes are removed,
	 * then the call of clear_state() can be moved to just before the
	 * while() - statment. *sigh* /Lars
	 */
	/* amylaar: I think inconsistencys should be found, rather than
	 * the effects patched
	 */
#ifdef DEBUF
	if ( check_state() ) {
	    debug_message("Inconsistency in main loop\n");
	    dump_trace(1);
	    shout_string("Gamedriver shouts: I feel inconsistent!\n");
	    clear_state();
	}
#endif
	CLEAR_EVAL_COST;
#ifndef __GNUC__
	alloca(0); /* free alloca'd values from deeper levels of nesting */
#endif
	remove_destructed_objects(); /* marion - before ref checks! */
#ifdef DEBUG
	if (d_flag > 1)
	    check_a_lot_ref_counts(0);
#endif
	if (extra_jobs_to_do) {
	    current_interactive = 0;
	    if (game_is_being_shut_down) {
		command_giver = 0; /* This statement was removed from the end
				    * of the main loop. We have to compensate
				    * for this.
	    			    */
		shutdowngame();
	    }
	    if (master_will_be_updated) {
		extern struct object dummy_current_object_for_loads;

		emergency_destruct(master_ob);
		master_will_be_updated = 0;
		/* maybe you'll want the new master to reload the player files
		 * as well :-)
		 */
		current_object = &dummy_current_object_for_loads;
		apply_master_ob("external_master_reload", 0);
		current_object = 0;
	    }
	    if (garbage_collect_to_do) {
		extern void garbage_collection();

		command_giver = 0;
		garbage_collection();
		garbage_collect_to_do = 0;
		if (slow_shut_down_to_do) {
		    int tmp = slow_shut_down_to_do;
		    slow_shut_down_to_do = 0;
		    malloc_privilege = MALLOC_MASTER;
		    slow_shut_down(tmp);
		}
		malloc_privilege = MALLOC_USER;
	    }
	    extra_jobs_to_do = 0;
	    if (num_dirty_mappings) {
		extern void compact_mappings PROT((mp_int));

		compact_mappings(num_dirty_mappings+80 >> 5);
		malloc_privilege = MALLOC_USER;
	    }
	}
	if (get_message(buff, sizeof buff)) {
	    void update_load_av PROT((void));

	    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 player_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 = 0;
	    current_interactive = command_giver;

#ifdef DEBUG
	    if (!command_giver->interactive)
		fatal("Non interactive player in main loop !\n");
#endif
	    tracedepth = 0;
	    if (buff[0] == '!' && command_giver->super) {
		if (command_giver->interactive->noecho) {
		    add_message("%s\n", buff);
		}
		parse_command(buff+1, command_giver);
	    } else if (command_giver->interactive->ed_buffer)
		ed_cmd(buff);
	    else if (call_function_interactive(command_giver->interactive,buff))
		;	/* Do nothing ! */
	    else
		parse_command(buff, command_giver);
	    /*
	     * Print a prompt if player is still here.
	     */
	    if (command_giver && command_giver->interactive)
		print_prompt();
	}
	if (time_to_call_heart_beat)
	    call_heart_beat();
    } /* end of main loop */
}

/*
 * Despite the name, this routine takes care of several things.
 * It will loop through all objects once every 15 minutes.
 *
 * If an object is found in a state of not having done reset, and the
 * delay to next reset has passed, then reset() will be done.
 *
 * If the object has a existed more than the time limit given for swapping,
 * then 'clean_up' will first be called in the object, after which it will
 * be swapped out if it still exists.
 *
 * There are some problems if the object self-destructs in clean_up, so
 * special care has to be taken of how the linked list is used.
 */
static void look_for_objects_to_swap() {
    extern long time_to_swap; /* marion - for invocation parameter */
    static int next_time;
    static struct object *next_ob; /* don't change back with longjmp() */
    struct object *ob;
    struct error_recovery_info error_recovery_info;
    union {
	double dummy; /* force alignment */
	char c[sizeof(struct program)+SCAN_SWAP_BUFSIZE];
    } swapbuf;

    if (current_time < next_time)
	return;				/* Not time to look yet */
#if TIME_TO_SWAP >= 15 * 60
    next_time = current_time + 15 * 60;	/* Next time is in 15 minutes */
#else
    next_time = current_time + TIME_TO_SWAP;
#endif
    /*
     * Objects object can be destructed, which means that
     * next object to investigate is saved in next_ob. If very unlucky,
     * that object can be destructed too. In that case, the loop is simply
     * restarted.
     */
    set_swapbuf(swapbuf.c);
    next_ob = obj_list;
    error_recovery_info.last = error_recovery_pointer;
    error_recovery_info.type = ERROR_RECOVERY_BACKEND;
    error_recovery_pointer = &error_recovery_info;
    if (setjmp(error_recovery_info.context)) {		/* amylaar */
        clear_state();
        debug_message("Error in look_for_objects_to_swap.\n");
    }
    for (; ob = next_ob; ) {
	int time_since_ref;
	if (ob->flags & O_DESTRUCTED) {
	    ob = obj_list; /* restart */
	}
	next_ob = ob->next_all;
	/*
	 * Check reference time before reset() is called.
	 */
	time_since_ref = current_time - ob->time_of_ref;
	/*
	 * Should this object have reset(1) called ?
	 */
	if (ob->next_reset < current_time && !(ob->flags & O_RESET_STATE)) {
	    if (d_flag)
		fprintf(stderr, "RESET %s\n", ob->name);
	    CLEAR_EVAL_COST;
	    command_giver = 0;
	    trace_level = 0;
	    reset_object(ob, 1);
	}
#if TIME_TO_CLEAN_UP > 0
	/*
	 * Has enough time passed, to give the object a chance
	 * to self-destruct ? Save the O_RESET_STATE, which will be cleared.
	 *
	 * Only call clean_up in objects that has defined such a function.
	 *
	 * Only if the clean_up returns a non-zero value, will it be called
	 * again.
	 */
	else if (time_since_ref > TIME_TO_CLEAN_UP &&
	    (ob->flags & O_WILL_CLEAN_UP))
	{
	    int was_swapped = ob->flags & O_SWAPPED ;
	    int save_reset_state = ob->flags & O_RESET_STATE;
	    struct svalue *svp;

	    if (d_flag)
		fprintf(stderr, "clean up %s\n", ob->name);
	    /*
	     * 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).
	     */
	    push_number(ob->flags & O_CLONE ? 0 :
	      ( ob->flags & O_SWAPPED ? 1 : ob->prog->ref) );
	    CLEAR_EVAL_COST;
	    command_giver = 0;
	    trace_level = 0;
	    svp = apply("clean_up", ob, 1);
	    if (ob->flags & O_DESTRUCTED)
		continue;
	    if ((!svp || (svp->type == T_NUMBER && svp->u.number == 0)) &&
		was_swapped )
		ob->flags &= ~O_WILL_CLEAN_UP;
	    ob->flags |= save_reset_state;
	}
#endif /* TIME_TO_CLEAN_UP > 0 */
#if TIME_TO_SWAP > 0
	/*
	 * At last, there is a possibility that the object can be swapped
	 * out.
	 */
	if (ob->flags & O_SWAPPED || time_since_ref < time_to_swap)
	    continue;
	if (ob->flags & O_HEART_BEAT)
	    continue;
	if (d_flag)
	    fprintf(stderr, "swap %s\n", ob->name);
	swap(ob);	/* See if it is possible to swap out to disk */
#endif
    }
    set_swapbuf((char *)0);
    error_recovery_pointer = error_recovery_info.last;
}

/*
 * Call all heart_beat() functions in all objects.  Also call the next reset,
 * and the call out.
 * We do heart beats by moving each object done to the end of the heart beat
 * list before we call its function, and always using the item at the head
 * of the list as our function to call.  We keep calling heart beats until
 * a timeout or we have done num_heart_objs calls.  It is done this way so
 * that objects can delete heart beating objects from the list from within
 * their heart beat without truncating the current round of heart beats.
 *
 * Set command_giver to current_object if it is a living object. If the object
 * is shadowed, check the shadowed object if living. There is no need to save
 * the value of the command_giver, as the caller resets it to 0 anyway.
 */
static struct object * hb_list = 0; /* head */
static struct object * hb_tail = 0; /* for sane wrap around */

static int num_hb_objs = 0;  /* so we know when to stop! */
static int num_hb_calls = 0; /* stats */
static float perc_hb_probes = 100.0; /* decaying avge of how many complete */

void call_heart_beat() {
    struct object *ob, *hide_current = current_object;
    int num_done = 0;
    
    time_to_call_heart_beat = 0; /* interrupt loop if we take too long */
    comm_time_to_call_heart_beat = 0;
#ifndef MSDOS
    /* If the host is swapping madly, it can be too late here to reinstate
     * the SIGALRM handler here.
     */
    alarm(2);

#else
    start_timer(2);
#endif
    current_time = get_current_time();
    current_interactive = 0;

    if ((num_player > 0) && hb_list) {
        num_hb_calls++;
	while (hb_list &&
#ifndef MSDOS
	       !comm_time_to_call_heart_beat
#else
	       !timer_expired()
#endif
	       && (num_done < num_hb_objs)) {
	    num_done++;
	    cycle_hb_list();
	    ob = hb_tail; /* now at end */
#ifdef DEBUG
	    if (!(ob->flags & O_HEART_BEAT))
		fatal("Heart beat not set in object on heart beat list!");
	    if (ob->flags & O_SWAPPED)
		fatal("Heart beat in swapped object.\n");
#endif
	    /* move ob to end of list, do ob */
	    if (ob->prog->heart_beat == -1)
		continue;
	    current_prog = ob->prog;
	    current_object = ob;
	    current_heart_beat = ob;
	    command_giver = ob;
	    while(command_giver->shadowing)
		command_giver = command_giver->shadowing;
	    if (!(command_giver->flags & O_ENABLE_COMMANDS)) {
		command_giver = 0;
		trace_level = 0;
	    } else
		trace_level = command_giver->interactive ?
			command_giver->interactive->trace_level : 0;
	    if (ob->user)
		ob->user->heart_beats++;
	    CLEAR_EVAL_COST;
	    call_function(ob->prog, ob->prog->heart_beat);
	}
	if (num_hb_objs)
	    perc_hb_probes = 100 * (float) num_done / num_hb_objs;
	else
	    perc_hb_probes = 100.0;
    }
    current_heart_beat = 0;
    current_object = 0;
    look_for_objects_to_swap();
    call_out();	/* some things depend on this, even without players! */
    command_giver = 0;
    trace_level = 0;
    current_object = hide_current;
    flush_all_player_mess();
    wiz_decay();
#ifdef MUDWHO
    sendmudwhoinfo();
#endif
}

/*
 * Take the first object off the heart beat list, place it at the end
 */
static void cycle_hb_list()
{
    struct object * ob;
    if (!hb_list)
	fatal("Cycle heart beat list with empty list!");
    if (hb_list == hb_tail)
	return; /* 1 object on list */
    ob = hb_list;
    hb_list = hb_list -> next_heart_beat;
    hb_tail -> next_heart_beat = ob;
    hb_tail = ob;
    ob->next_heart_beat = 0;
}

/*
 * add or remove an object from the heart beat list; does the major check...
 * If an object removes something from the list from within a heart beat,
 * various pointers in call_heart_beat could be stuffed, so we must
 * check current_heart_beat and adjust pointers.
 */

int set_heart_beat(ob, to)
    struct object * ob;
    int to;
{
    struct object * o = hb_list;
    struct object * oprev = 0;

    if (ob->flags & O_DESTRUCTED)
	return 0;
    if (to)
	to = 1;

    while (o && o != ob) {
	if (!(o->flags & O_HEART_BEAT))
	    fatal("Found disabled object in the active heart beat list!\n");
	oprev = o;
	o = o->next_heart_beat;
	}

    if (!o && (ob->flags & O_HEART_BEAT))
	fatal("Couldn't find enabled object in heart beat list!");
    
    if (to == ((ob->flags & O_HEART_BEAT) != 0))
	return(0);

    if (to) {
	ob->flags |= O_HEART_BEAT;
	if (ob->next_heart_beat)
	    fatal("Dangling pointer to next_heart_beat in object!");
	ob->next_heart_beat = hb_list;
	hb_list = ob;
	if (!hb_tail) hb_tail = ob;
	num_hb_objs++;
	cycle_hb_list();     /* Added by Linus. 911104 */
    }
    else { /* remove all refs */
	ob->flags &= ~O_HEART_BEAT;
	if (hb_list == ob)
	    hb_list = ob->next_heart_beat;
	if (hb_tail == ob)
	    hb_tail = oprev;
	if (oprev)
	    oprev->next_heart_beat = ob->next_heart_beat;
	ob->next_heart_beat = 0;
	num_hb_objs--;
	}

    return(1);
}
/*
 * sigh.  Another status function.
 */
int heart_beat_status(verbose)
    int verbose;
{
    char buf[20];

    if (verbose) {
	add_message("\nHeart beat information:\n");
	add_message("-----------------------\n");
	add_message("Number of objects with heart beat: %d, starts: %d\n",
		    num_hb_objs, num_hb_calls);
	sprintf(buf, "%.2f", perc_hb_probes);
	add_message("Percentage of HB calls completed last time: %s\n", buf);
    }
    return 0;
}

/*
 * There is a file with a list of objects to be initialized at
 * start up.
 */

void load_first_objects() { /* Old version used when o_flag true /JnA */
    FILE *f;
    char buff[1000];
    char *p;
    extern int e_flag;
#ifdef AMIGA
#define tms_total(tms) (tms)
#define times(tmsp) ( *(tmsp) = clock() )
    clock_t tms1, tms2;
#else
#ifdef MSDOS
#define tms_total(tms) (tms)
#define times(tmsp) milliseconds(tmsp)
    long tms1 = 0L, tms2;
#else
#define tms_total(tms) ((tms).tms_utime + (tms).tms_stime)
    struct tms tms1, tms2;
#endif
#endif

    if (e_flag)
	return;
    (void)printf("Loading init file %s\n", INIT_FILE);
    f = fopen(INIT_FILE, "r");
    if (f == 0)
	return;
    toplevel_error_recovery_info.type = ERROR_RECOVERY_BACKEND;
    if (setjmp(toplevel_error_recovery_info.context)) {
	clear_state();
	add_message("Anomaly in the fabric of world space.\n");
    }
    times(&tms1);
    current_object = master_ob;
    while(1) {
	if (fgets(buff, sizeof buff, f) == NULL)
	    break;
	if (buff[0] == '#')
	    continue;
	p = strchr(buff, '\n');
	if (p != 0)
	    *p = 0;
	if (buff[0] == '\0')
	    continue;
	(void)printf("Preloading: %s", buff);
	fflush(stdout);
	CLEAR_EVAL_COST;
	(void)find_object(buff);
#ifdef MALLOC_malloc
	resort_free_list();
#endif
	times(&tms2);
	(void)printf(" %.2f\n", ( tms_total(tms2) - tms_total(tms1) ) /
				  (double)CLOCKS_PER_SEC);
	tms1 = tms2;
	fflush(stdout);
    }
    toplevel_error_recovery_info.type = ERROR_RECOVERY_NONE;
    fclose(f);
}

/*
 * New version used when not in -o mode. The epilog() in master.c is
 * supposed to return an array of files (castles in 2.4.5) to load. The array
 * returned by apply() will be freed at next call of apply(), which means that
 * the ref count has to be incremented to protect against deallocation.
 *
 * The master object is asked to do the actual loading.
 */
void preload_objects(eflag)
    int eflag;
{
    struct vector *prefiles;
    struct svalue *ret;
    static int ix0;
    int ix;

    push_number(eflag);
    ret = apply_master_ob("epilog", 1);

    if ((ret == 0) || (ret->type != T_POINTER))
	return;
    else
	prefiles = ret->u.vec;

    if ((prefiles == 0) || (prefiles->size < 1))
	return;

    prefiles->ref++;

    ix0 = -1;
    toplevel_error_recovery_info.type = ERROR_RECOVERY_BACKEND;
    if (setjmp(toplevel_error_recovery_info.context)) {
	clear_state();
	add_message("Anomaly in the fabric of world space.\n");
    }

    while ((ix = ++ix0) < prefiles->size) {
	if (prefiles->item[ix].type != T_STRING)
	    continue;

	CLEAR_EVAL_COST;
	push_string_malloced(prefiles->item[ix].u.string);
	(void)apply_master_ob("preload", 1);

#ifdef MALLOC_malloc
	resort_free_list();
#endif
    }
    free_vector(prefiles);
    toplevel_error_recovery_info.type = ERROR_RECOVERY_NONE;
}

/*
 * catch alarm, set flag for comms code and heart_beat to catch.
 * comms code sets time_to_call_heart_beat for the backend when
 * it has completed the current round of player commands.
 */

void catch_alarm() {
#ifndef MSDOS
    (void)signal(SIGALRM, catch_alarm);
#endif
    comm_time_to_call_heart_beat = 1;
}

/*
 * All destructed objects are moved int a sperate linked list,
 * and deallocated after program execution.
 */
void remove_destructed_objects()
{
    struct object *ob, *next;
    if (obj_list_replace) {
	extern void replace_programs();
	replace_programs();
    }
    for (ob=obj_list_destruct; ob; ob = next) {
	next = ob->next_all;
	destruct2(ob);
    }
    obj_list_destruct = 0;
}

/*
 * Append string to file. Return 0 for failure, otherwise 1.
 */
int write_file(file, str)
    char *file;
    char *str;
{
    FILE *f;

    file = check_valid_path(file, current_object, "write_file", 1);
    if (!file)
	return 0;
    f = fopen(file, "a");
    if (f == 0) {
	if (errno == EMFILE || errno == ENFILE) {
	    extern void lex_close();
	    extern void push_apply_value(), pop_apply_value();

	    /* lex_close() calls lexerror(). lexerror() calls yyerror().
	     * yyerror calls smart_log(). smart_log calls apply_master_ob().
	     * This is why the value of file needs to be preserved.
	     */
	    push_apply_value();
	    lex_close();
	    pop_apply_value();
	    f = fopen(file, "a");
	}
	if (f == 0) {
	    perror("write_file");
	    error("Wrong permissions for opening file %s for append.\n", file);
	}
    }
    fwrite(str, strlen(str), 1, f);
    fclose(f);
    return 1;
}

#if ( defined( atarist ) && !defined ( minix ) ) || defined( MSDOS )
#define MSDOS_FS
#endif

char *read_file(file,start,len)
    char *file;
    int start,len;
{
    struct stat st;
    FILE *f;
    char *str,*p,*p2,*end,c;
    int size;

    if (len < 0 && len != -1) return 0;
    file = check_valid_path(file, current_object, "read_file", 0);

    if (!file)
	return 0;
    f = fopen(file, "r");
    if (f == 0)
	return 0;
    if (fstat(fileno(f), &st) == -1)
	fatal("Could not stat an open file.\n");
    size = st.st_size;
    if (size > READ_FILE_MAX_SIZE) {
	if ( start || len ) size = READ_FILE_MAX_SIZE;
	else {
	    fclose(f);
	    return 0;
	}
    }
    if (!start) start = 1;
    if (!len) len = READ_FILE_MAX_SIZE;
    str = xalloc(size + 2);
    if (!str) {
	fclose(f);
	return 0;
    }
    *str++ = ' '; /* this way, we can always read the 'previous' char... */
    str[size] = '\0';
    do {
	if (size > st.st_size)
	    size = st.st_size;
        if (!size && start > 1 || fread(str, size, 1, f) != 1) {
    	    fclose(f);
	    xfree(str-1);
    	    return 0;
        }
	st.st_size -= size;
	end = str+size;
        for (p=str; ( p2=memchr(p,'\n',end-p) ) && --start; ) p=p2+1;
    } while ( start > 1 );
    for (p2=str; p != end; ) {
        c = *p++;
	if ( !isprint(c) && !isspace(c) ) c=' ';
	if ( c == '\n' ) {
#ifdef MSDOS_FS
	    if ( p2[-1] == '\r' ) p2--;
#endif
	    if (!--len) {
		*p2++=c;
	        break;
	    }
	}
	*p2++=c;
    }
    if ( len && st.st_size ) {
	size -= ( p2-str) ; 
	if (size > st.st_size)
	    size = st.st_size;
        if (fread(p2, size, 1, f) != 1) {
    	    fclose(f);
	    xfree(str-1);
    	    return 0;
        }
	st.st_size -= size;
	end = p2+size;
        for (p=p2; p != end; ) {
	    c = *p++;
	    if ( !isprint(c) && !isspace(c) ) c=' ';
	    if ( c == '\n' ) {
#ifdef MSDOS_FS
	        if ( p2[-1] == '\r' ) p2--;
#endif
		if (!--len) {
		    *p2++ = c;
		    break;
		}
	    }
	    *p2++ = c;
	}
	if ( st.st_size && len > 0) {
	    /* tried to read more than READ_MAX_FILE_SIZE */
	    fclose(f);
	    xfree(str-1);
	    return 0;
	}
    }
    *p2='\0';
    fclose(f);
#if 0
    if ( st.st_size = (p2-str) )
	return str;
#endif
    p2=string_copy(str);
    xfree(str-1);
    return p2;
}


char *read_bytes(file,start,len)
    char *file;
    int start,len;
{
    struct stat st;

    char *str,*p;
    int size, f;

    if (len < 0)
	return 0;
    if(len > MAX_BYTE_TRANSFER)
	return 0;
    file = check_valid_path(file, current_object, "read_bytes", 0);

    if (!file)
	return 0;
    f = ixopen(file, O_RDONLY);
    if (f < 0)
	return 0;

    if (fstat(f, &st) == -1)
	fatal("Could not stat an open file.\n");
    size = st.st_size;
    if(start < 0) 
	start = size + start;

    if (start >= size) {
	close(f);
	return 0;
    }
    if ((start+len) > size) 
	len = (size - start);

    if ((size = lseek(f,start, 0)) < 0) {
	close(f);
	return 0;
    }

    str = xalloc(len + 1);
    if (!str) {
	close(f);
	return 0;
    }

    size = read(f, str, len);

    close(f);

    if (size <= 0) {
	xfree(str);
	return 0;
    }

    /* We want to allow all characters to pass untouched!
    for (il=0;il<size;il++) 
	if (!isprint(str[il]) && !isspace(str[il]))
	    str[il] = ' ';

    str[il] = 0;
    */
    /*
     * The string has to end to '\0'!!!
     */
    str[size] = '\0';

    p = string_copy(str);
    xfree(str);

    return p;
}

int write_bytes(file,start,str)
    char *file, *str;
    int start;
{
    struct stat st;

    mp_int size, len;
    int f;

    file = check_valid_path(file, current_object, "write_bytes", 1);

    if (!file)
	return 0;
    len = strlen(str);
    if(len > MAX_BYTE_TRANSFER)
	return 0;
    f = ixopen(file, O_WRONLY);
    if (f < 0)
	return 0;

    if (fstat(f, &st) == -1)
	fatal("Could not stat an open file.\n");
    size = st.st_size;
    if(start < 0) 
	start = size + start;

    if (start > size) {
	close(f);
	return 0;
    }
    if ((size = lseek(f,start, 0)) < 0) {
	close(f);
	return 0;
    }

    size = write(f, str, len);

    close(f);

    if (size <= 0) {
	return 0;
    }

    return 1;
}


int file_size(file)
    char *file;
{
    struct stat st;

    file = check_valid_path(file, current_object, "file_size", 0);
    if (!file)
	return -1;
    if (ixstat(file, &st) == -1)
	return -1;
    if (S_IFDIR & st.st_mode)
	return -2;
    return st.st_size;
}

static double load_av = 0.0;

void update_load_av() {
    extern double consts[5];
    extern int current_time;
    static int last_time;
    int n;
    double c;
    static int acc = 0;

    acc++;
    if (current_time == last_time)
	return;
    n = current_time - last_time;
    if (n < 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;
}

static double compile_av = 0.0;

void update_compile_av(lines)
    int lines;
{
    extern double consts[5];
    extern int current_time;
    static int last_time;
    int n;
    double c;
    static int acc = 0;

    acc += lines;
    if (current_time == last_time)
	return;
    n = current_time - last_time;
    if (n < 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;
}

char *query_load_av() {
    static char buff[100];

    sprintf(buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av);
    return buff;
}

/*
 * Constructs an array of all objects that have a heart_beat.
 */
struct svalue *heart_beat_info(sp)
    struct svalue *sp;
{
    int i;
    struct object *ob;
    struct vector *vec;
    struct svalue *v;

    for (i = 0, ob = hb_list; ob; i++, ob = ob->next_heart_beat);
    vec = allocate_array(i);
    for (v = vec->item, ob = hb_list; ob; v++, ob = ob->next_heart_beat) {
	v->type = T_OBJECT;
	v->u.ob = ob;
	add_ref(ob, "heart_beat_info");
    }
    sp++;
    sp->type = T_POINTER;
    sp->u.vec = vec;
    return sp;
}