/
MOO-1.8.0p5/
/******************************************************************************
  Copyright (c) 1995, 1996 Xerox Corporation.  All rights reserved.
  Portions of this code were written by Stephen White, aka ghond.
  Use and copying of this software and preparation of derivative works based
  upon this software are permitted.  Any distribution of this software or
  derivative works must comply with all applicable United States export
  control laws.  This software is made available AS IS, and Xerox Corporation
  makes no warranty about the software, its performance or its conformity to
  any specification.  Any person obtaining a copy of this software is requested
  to send their name and post office or electronic mail address to:
    Pavel Curtis
    Xerox PARC
    3333 Coyote Hill Rd.
    Palo Alto, CA 94304
    Pavel@Xerox.Com
 *****************************************************************************/

/*****************************************************************************
 * Routines for initializing, loading, dumping, and shutting down the database
 *****************************************************************************/

#include "my-stat.h"
#include "my-stdio.h"
#include "my-stdlib.h"

#include "config.h"
#include "db.h"
#include "db_io.h"
#include "db_private.h"
#include "exceptions.h"
#include "list.h"
#include "log.h"
#include "options.h"
#include "server.h"
#include "storage.h"
#include "streams.h"
#include "tasks.h"
#include "timers.h"
#include "version.h"

static char	       *input_db_name, *dump_db_name;
static int		dump_generation = 0;
static const char      *header_format_string
    = "** LambdaMOO Database, Format Version %u **\n";

DB_Version		dbio_input_version;


/*********** Verb and property I/O ***********/

static void
read_verbdef(Verbdef *v)
{
    v->name = str_dup(dbio_read_string());
    v->owner = dbio_read_objid();
    v->perms = dbio_read_num();
    v->prep = dbio_read_num();
    v->next = 0;
    v->program = 0;
}

static void
write_verbdef(Verbdef *v)
{
    dbio_write_string(v->name);
    dbio_write_objid(v->owner);
    dbio_write_num(v->perms);
    dbio_write_num(v->prep);
}

static Propdef
read_propdef()
{
    char *name = str_dup(dbio_read_string());
    return dbpriv_new_propdef(name);
}

static void
write_propdef(Propdef *p)
{
    dbio_write_string(p->name);
}

static void
read_propval(Pval *p)
{
    p->var = dbio_read_var();
    p->owner = dbio_read_objid();
    p->perms = dbio_read_num();
}

static void
write_propval(Pval *p)
{
    dbio_write_var(p->var);
    dbio_write_objid(p->owner);
    dbio_write_num(p->perms);
}


/*********** Object I/O ***********/

static int
read_object(void)
{
    Objid	oid;
    Object     *o;
    char	s[20];
    int		i;
    Verbdef    *v, **prevv;
    int		nprops;

    if (dbio_scanf("#%d", &oid) != 1  ||  oid != db_last_used_objid() + 1)
	return 0;
    dbio_read_line(s, sizeof(s));

    if (strcmp(s, " recycled\n") == 0) {
	dbpriv_new_recycled_object();
	return 1;
    } else if (strcmp(s, "\n") != 0)
	return 0;

    o = dbpriv_new_object();
    o->name = str_dup(dbio_read_string());
    (void) dbio_read_string();	/* discard old handles string */
    o->flags = dbio_read_num();

    o->owner = dbio_read_objid();

    o->location = dbio_read_objid();
    o->contents = dbio_read_objid();
    o->next = dbio_read_objid();

    o->parent = dbio_read_objid();
    o->child = dbio_read_objid();
    o->sibling = dbio_read_objid();

    o->verbdefs = 0;
    prevv = &(o->verbdefs);
    for (i = dbio_read_num(); i > 0; i--) {
	v = mymalloc(sizeof(Verbdef), M_VERBDEF);
	read_verbdef(v);
	*prevv = v;
	prevv = &(v->next);
    }

    o->propdefs.cur_length = 0;
    o->propdefs.max_length = 0;
    o->propdefs.l = 0;
    if ((i = dbio_read_num()) != 0) {
	o->propdefs.l = mymalloc(i * sizeof(Propdef), M_PROPDEF);
	o->propdefs.cur_length = i;
	o->propdefs.max_length = i;
	for (i = 0; i < o->propdefs.cur_length; i++)
	    o->propdefs.l[i] = read_propdef();
    }
	
    nprops = dbio_read_num();
    if (nprops)
	o->propval = mymalloc(nprops * sizeof(Pval), M_PVAL);
    else
	o->propval = 0;

    for (i = 0; i < nprops; i++) {
	read_propval(o->propval + i);
    }

    return 1;
}

static void
write_object(Objid oid)
{
    Object     *o;
    Verbdef    *v;
    int		i;
    int		nverbdefs, nprops;

    if (!valid(oid)) {
	dbio_printf("#%d recycled\n", oid);
	return;
    }
    o = dbpriv_find_object(oid);

    dbio_printf("#%d\n", oid);
    dbio_write_string(o->name);
    dbio_write_string("");	/* placeholder for old handles string */
    dbio_write_num(o->flags);

    dbio_write_objid(o->owner);

    dbio_write_objid(o->location);
    dbio_write_objid(o->contents);
    dbio_write_objid(o->next);

    dbio_write_objid(o->parent);
    dbio_write_objid(o->child);
    dbio_write_objid(o->sibling);

    for (v = o->verbdefs, nverbdefs = 0; v; v = v->next)
	nverbdefs++;

    dbio_write_num(nverbdefs);
    for (v = o->verbdefs; v; v = v->next)
	write_verbdef(v);

    dbio_write_num(o->propdefs.cur_length);
    for (i = 0; i < o->propdefs.cur_length; i++)
	write_propdef(&o->propdefs.l[i]);
    
    nprops = dbpriv_count_properties(oid);

    dbio_write_num(nprops);
    for (i = 0; i < nprops; i++)
	write_propval(o->propval + i);
}


/*********** File-level Input ***********/

static int
validate_hierarchies()
{
    Objid	oid, log_oid;
    Objid	size = db_last_used_objid() + 1;
    int		broken = 0;
    int		fixed_nexts = 0;

    oklog("VALIDATING the object hierarchies ...\n");

#   define PROGRESS_INTERVAL 10000
#   define MAYBE_LOG_PROGRESS					\
    {								\
        if (oid == log_oid) {					\
	    log_oid += PROGRESS_INTERVAL;			\
	    oklog("VALIDATE: Done through #%d ...\n", oid);	\
	}							\
    }

    oklog("VALIDATE: Phase 1: Check for invalid objects ...\n");
    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
	Object *o = dbpriv_find_object(oid);

	MAYBE_LOG_PROGRESS;
	if (o) {
	    if (o->location == NOTHING  &&  o->next != NOTHING) {
		o->next = NOTHING;
		fixed_nexts++;
	    }

#	    define CHECK(field, name) 					\
	    {								\
	        if (o->field != NOTHING					\
		    && !dbpriv_find_object(o->field)) {			\
		    errlog("VALIDATE: #%d.%s = #%d <invalid> ... fixed.\n", \
			   oid, name, o->field);			\
		    o->field = NOTHING;				  	\
		}							\
	    }

	    CHECK(parent, "parent");
	    CHECK(child, "child");
	    CHECK(sibling, "sibling");
	    CHECK(location, "location");
	    CHECK(contents, "contents");
	    CHECK(next, "next");

#	    undef CHECK
	}
    }

    if (fixed_nexts != 0)
	errlog("VALIDATE: Fixed %d should-be-null next pointer(s) ...\n",
	       fixed_nexts);

    oklog("VALIDATE: Phase 2: Check for cycles ...\n");
    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
	Object	*o = dbpriv_find_object(oid);

	MAYBE_LOG_PROGRESS;
	if (o) {
#	    define CHECK(start, field, name)				\
	    {								\
	        Objid	oid2 = start;					\
		int	count = 0;					\
	        for (; oid2 != NOTHING					\
		     ; oid2 = dbpriv_find_object(oid2)->field) {	\
		    if (++count > size)	{				\
			errlog("VALIDATE: Cycle in `%s' chain of #%d\n",\
			       name, oid);				\
			broken = 1;					\
			break;						\
		    }							\
		}							\
	    }

	    CHECK(o->parent, parent, "parent");
	    CHECK(o->child, sibling, "child");
	    CHECK(o->location, location, "location");
	    CHECK(o->contents, next, "contents");

#	    undef CHECK
	}
    }

    if (broken)			/* Can't continue if cycles found */
	return 0;

    oklog("VALIDATE: Phase 3: Check for inconsistencies ...\n");
    for (oid = 0, log_oid = PROGRESS_INTERVAL; oid < size; oid++) {
	Object	*o = dbpriv_find_object(oid);

	MAYBE_LOG_PROGRESS;
	if (o) {
#	    define CHECK(up, up_name, down, down_name, across)		\
	    {								\
		Objid	up = o->up;					\
		Objid	oid2;						\
									\
		/* Is oid in its up's down list? */			\
		if (up != NOTHING) {					\
		    for (oid2 = dbpriv_find_object(up)->down;		\
			 oid2 != NOTHING;				\
			 oid2 = dbpriv_find_object(oid2)->across) {	\
			if (oid2 == oid) /* found it */			\
			    break;					\
		    }							\
		    if (oid2 == NOTHING) { /* didn't find it */		\
			errlog("VALIDATE: #%d not in %s (#%d)'s %s list.\n", \
			       oid, up_name, up, down_name);	        \
			broken = 1;					\
		    }							\
		}							\
	    }

	    CHECK(parent, "parent", child, "child", sibling);
	    CHECK(location, "location", contents, "contents", next);

#	    undef CHECK

#	    define CHECK(up, down, down_name, across)			\
	    {								\
		Objid	oid2;						\
									\
		for (oid2 = o->down;					\
		     oid2 != NOTHING;					\
		     oid2 = dbpriv_find_object(oid2)->across) {		\
		    if (dbpriv_find_object(oid2)->up != oid) {		\
			errlog(						\
			    "VALIDATE: #%d erroneously on #%d's %s list.\n", \
			    oid2, oid, down_name);			\
			broken = 1;					\
		    }							\
		}							\
	    }

	    CHECK(parent, child, "child", sibling);
	    CHECK(location, contents, "contents", next);
	    
#	    undef CHECK
	}
    }
    
    oklog("VALIDATING the object hierarchies ... finished.\n");
    return !broken;
}

static const char *
fmt_verb_name(void *data)
{
    db_verb_handle     *h = data;
    static Stream      *s = 0;

    if (!s)
	s = new_stream(40);

    stream_printf(s, "#%d:%s", db_verb_definer(*h), db_verb_names(*h));
    return reset_stream(s);
}

static int
read_db_file(void)
{
    Objid		oid;
    int			nobjs, nprogs, nusers;
    Var			user_list;
    int			i, vnum, dummy;
    db_verb_handle	h;
    Program	       *program;

    if (dbio_scanf(header_format_string, &dbio_input_version) != 1)
	dbio_input_version = DBV_Prehistory;

    if (!check_version(dbio_input_version)) {
	errlog("READ_DB_FILE: Unknown DB version number: %d\n",
		dbio_input_version);
	return 0;
    }

    /* I use a `dummy' variable here and elsewhere instead of the `*'
     * assignment-suppression syntax of `scanf' because it allows more
     * straightforward error checking; unfortunately, the standard says that
     * suppressed assignments are not counted in determining the returned value
     * of `scanf'...
     */
    if (dbio_scanf("%d\n%d\n%d\n%d\n",
		   &nobjs, &nprogs, &dummy, &nusers) != 4) {
	errlog("READ_DB_FILE: Bad header\n");
	return 0;
    }

    user_list = new_list(nusers);
    for (i = 1; i <= nusers; i++) {
	user_list.v.list[i].type = TYPE_OBJ;
	user_list.v.list[i].v.obj = dbio_read_objid();
    }
    dbpriv_set_all_users(user_list);

    oklog("LOADING: Reading %d objects...\n", nobjs);
    for (i = 1; i <= nobjs; i++) {
	if (!read_object()) {
	    errlog("READ_DB_FILE: Bad object #%d.\n", i - 1);
	    return 0;
	}
	if (i % 10000 == 0  ||  i == nobjs)
	    oklog("LOADING: Done reading %d objects ...\n", i);
    }

    if (!validate_hierarchies()) {
	errlog("READ_DB_FILE: Errors in object hierarchies.\n");
	return 0;
    }

    oklog("LOADING: Reading %d MOO verb programs...\n", nprogs);
    for (i = 1; i <= nprogs; i++) {
	if (dbio_scanf("#%d:%d\n", &oid, &vnum) != 2) {
	    errlog("READ_DB_FILE: Bad program header, i = %d.\n", i);
	    return 0;
	}
	if (!valid(oid)) {
	    errlog("READ_DB_FILE: Verb for non-existant object: #%d:%d.\n",
		    oid, vnum);
	    return 0;
	}
	h = db_find_indexed_verb(oid, vnum + 1); /* DB file is 0-based. */
	if (!h.ptr) {
	    errlog("READ_DB_FILE: Unknown verb index: #%d:%d.\n", oid, vnum);
	    return 0;
	}
	program = dbio_read_program(dbio_input_version, fmt_verb_name, &h);
	if (!program) {
	    errlog("READ_DB_FILE: Unparsable program #%d:%d.\n", oid, vnum);
	    return 0;
	}
	db_set_verb_program(h, program);
	if (i % 5000 == 0  ||  i == nprogs)
	    oklog("LOADING: Done reading %d verb programs...\n", i);
    }

    oklog("LOADING: Reading forked and suspended tasks...\n");
    if (!read_task_queue()) {
	errlog("READ_DB_FILE: Can't read task queue.\n");
	return 0;
    }

    oklog("LOADING: Reading list of formerly active connections...\n");
    if (!read_active_connections()) {
	errlog("DB_READ: Can't read active connections.\n");
	return 0;
    }

    return 1;
}


/*********** File-level Output ***********/

static int
write_db_file(const char *reason)
{
    Objid		oid;
    Objid		max_oid = db_last_used_objid();
    Verbdef	       *v;
    Var			user_list;
    int			i;
    volatile int	nprogs = 0;
    volatile int	success = 1;

    for (oid = 0; oid <= max_oid; oid++) {
	if (valid(oid))
	    for (v = dbpriv_find_object(oid)->verbdefs; v; v = v->next)
		if (v->program)
		    nprogs++;
    }
    
    user_list = db_all_users();
    
    TRY 
	dbio_printf(header_format_string, current_version);
	dbio_printf("%d\n%d\n%d\n%d\n",
		    max_oid + 1, nprogs, 0, user_list.v.list[0].v.num);
	for (i = 1; i <= user_list.v.list[0].v.num; i++)
	    dbio_write_objid(user_list.v.list[i].v.obj);
        oklog("%s: Writing %d objects...\n", reason, max_oid + 1);
	for (oid = 0; oid <= max_oid; oid++) {
	    write_object(oid);
	    if ((oid + 1) % 10000 == 0  ||  oid == max_oid)
		oklog("%s: Done writing %d objects...\n", reason, oid + 1);
	}
	oklog("%s: Writing %d MOO verb programs...\n", reason, nprogs);
	for (i = 0, oid = 0; oid <= max_oid; oid++)
	    if (valid(oid)) {
		int	vcount = 0;
		
		for (v = dbpriv_find_object(oid)->verbdefs; v; v = v->next) {
		    if (v->program) {
			dbio_printf("#%d:%d\n", oid, vcount);
			dbio_write_program(v->program);
			if (++i % 5000 == 0  ||  i == nprogs)
			    oklog("%s: Done writing %d verb programs...\n",
				 reason, i);
		    }
		    vcount++;
		}
	    }

        oklog("%s: Writing forked and suspended tasks...\n", reason);
	write_task_queue();
        oklog("%s: Writing list of formerly active connections...\n", reason);
	write_active_connections();
    EXCEPT (dbpriv_dbio_failed)
	success = 0;
    ENDTRY;

    return success;
}

typedef enum { DUMP_SHUTDOWN, DUMP_CHECKPOINT, DUMP_PANIC } Dump_Reason;
const char *reason_names[] = { "DUMPING", "CHECKPOINTING", "PANIC-DUMPING" };

static int
dump_database(Dump_Reason reason)
{
    Stream     *s = new_stream(100);
    char       *temp_name;
    FILE       *f;
    int		success;

  retryDumping:

    stream_printf(s, "%s.#%d#", dump_db_name, dump_generation);
    remove(reset_stream(s));	/* Remove previous checkpoint */

    if (reason == DUMP_PANIC)
	stream_printf(s, "%s.PANIC", dump_db_name);
    else {
	dump_generation++;
	stream_printf(s, "%s.#%d#", dump_db_name, dump_generation);
    }
    temp_name = reset_stream(s);

    oklog("%s on %s ...\n", reason_names[reason], temp_name);

#ifdef UNFORKED_CHECKPOINTS
    reset_command_history();
#else
    if (reason == DUMP_CHECKPOINT) {
	switch (fork_server("checkpointer")) {
	  case FORK_PARENT:
	    reset_command_history();
	    free_stream(s);
	    return 1;
	  case FORK_ERROR:
	    free_stream(s);
	    return 0;
	  case FORK_CHILD:
	    set_server_cmdline("(MOO checkpointer)");
	    break;
	}
    }
#endif

    success = 1;
    if ((f = fopen(temp_name, "w")) != 0) {
	dbpriv_set_dbio_output(f);
	if (!write_db_file(reason_names[reason])) {
	    log_perror("Trying to dump database");
	    fclose(f);
	    remove(temp_name);
	    if (reason == DUMP_CHECKPOINT) {
		errlog("Abandoning checkpoint attempt...\n");
		success = 0;
	    } else {
		int	retry_interval = 60;

		errlog("Waiting %d seconds and retrying dump...\n",
		       retry_interval);
		timer_sleep(retry_interval);
		goto retryDumping;
	    }
	} else {
	    fclose(f);
	    oklog("%s on %s finished\n", reason_names[reason], temp_name);
	    if (reason != DUMP_PANIC) {
		remove(dump_db_name);
		if (rename(temp_name, dump_db_name) != 0) {
		    log_perror("Renaming temporary dump file");
		    success = 0;
		}
	    }
	}
    } else {
	log_perror("Opening temporary dump file");
	success = 0;
    }

    free_stream(s);

#ifndef UNFORKED_CHECKPOINTS
    if (reason == DUMP_CHECKPOINT)
	/* We're a child, so we'd better go away. */
	exit(!success);
#endif

    return success;
}


/*********** External interface ***********/

const char *
db_usage_string(void)
{
    return "input-db-file output-db-file";
}

static FILE    *input_db;

int
db_initialize(int *pargc, char ***pargv)
{
    FILE       *f;

    if (*pargc < 2)
	return 0;

    input_db_name = str_dup((*pargv)[0]);
    dump_db_name = str_dup((*pargv)[1]);
    *pargc -= 2;
    *pargv += 2;

    if (!(f = fopen(input_db_name, "r"))) {
	fprintf(stderr, "Cannot open input database file: %s\n",
		input_db_name);
	return 0;
    }

    input_db = f;
    dbpriv_build_prep_table();

    return 1;
}

int
db_load(void)
{
    dbpriv_set_dbio_input(input_db);

    oklog("LOADING: %s\n", input_db_name);
    if (!read_db_file()) {
	errlog("DB_LOAD: Cannot load database!\n");
	return 0;
    }
    oklog("LOADING: %s done, will dump new database on %s\n",
	input_db_name, dump_db_name);

    fclose(input_db);
    return 1;
}

int
db_flush(enum db_flush_type type)
{
    int success = 0;

    switch(type) {
      case FLUSH_IF_FULL:
      case FLUSH_ONE_SECOND:
	success = 1;
	break;

      case FLUSH_ALL_NOW:
	success = dump_database(DUMP_CHECKPOINT);
	break;

      case FLUSH_PANIC:
	success = dump_database(DUMP_PANIC);
	break;
    }

    return success;
}

int32
db_disk_size(void)
{
    struct stat	st;

    if ((dump_generation == 0  ||  stat(dump_db_name, &st) < 0)
	&& stat(input_db_name, &st) < 0)
	return -1;
    else
	return st.st_size;
}

void
db_shutdown()
{
    dump_database(DUMP_SHUTDOWN);
}

char rcsid_db_file[] = "$Id: db_file.c,v 2.5 1996/04/08 01:07:21 pavel Exp $";

/* $Log: db_file.c,v $
 * Revision 2.5  1996/04/08  01:07:21  pavel
 * Changed a boot-time error message to go directly to stderr, instead of
 * through the logging package.  Release 1.8.0p3.
 *
 * Revision 2.4  1996/02/08  07:20:18  pavel
 * Renamed err/logf() to errlog/oklog().  Updated copyright notice for 1996.
 * Release 1.8.0beta1.
 *
 * Revision 2.3  1995/12/31  03:27:54  pavel
 * Added missing #include "options.h".  Release 1.8.0alpha4.
 *
 * Revision 2.2  1995/12/28  00:51:39  pavel
 * Added db_disk_size().  Added support for printing location of
 * MOO-compilation warnings and errors during loading.  More slight
 * improvements to load-time progress messages.  Added dump-time progress
 * messages.  Added init-time call to build preposition table.
 * Release 1.8.0alpha3.
 *
 * Revision 2.1  1995/12/11  07:55:01  pavel
 * Added missing #include of "my-stdlib.h".  Slightly improved clarity of the
 * progress messages during DB loading.
 *
 * Release 1.8.0alpha2.
 *
 * Revision 2.0  1995/11/30  04:19:37  pavel
 * New baseline version, corresponding to release 1.8.0alpha1.
 *
 * Revision 1.1  1995/11/30  04:19:11  pavel
 * Initial revision
 */