mirror of
https://github.com/wrog/lambdamoo
synced 2026-08-13 00:26:05 -04:00
942 lines
22 KiB
C
942 lines
22 KiB
C
/******************************************************************************
|
||
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 "db.h"
|
||
|
||
#include "config.h"
|
||
#include "options.h"
|
||
|
||
#include "my-stat.h"
|
||
#include "my-unistd.h"
|
||
#include "my-stdio.h"
|
||
#include "my-stdlib.h"
|
||
#include "my-string.h"
|
||
|
||
#include "db_io.h"
|
||
#include "db_private.h"
|
||
#include "exceptions.h"
|
||
#include "list.h"
|
||
#include "log.h"
|
||
#include "server.h"
|
||
#include "storage.h"
|
||
#include "streams.h"
|
||
#include "str_intern.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 **";
|
||
|
||
DB_Version dbio_input_version;
|
||
|
||
|
||
/*********** Verb and property I/O ***********/
|
||
|
||
static int
|
||
read_verbdef(Verbdef * v)
|
||
{
|
||
v->next = 0;
|
||
v->program = 0;
|
||
return (dbio_read_string_intern(&v->name) &&
|
||
dbio_read_objid(&v->owner) &&
|
||
dbio_read_uint16(&v->perms) &&
|
||
dbio_read_int16(&v->prep));
|
||
}
|
||
|
||
static void
|
||
write_verbdef(Verbdef * v)
|
||
{
|
||
dbio_write_string(v->name);
|
||
dbio_write_objid(v->owner);
|
||
dbio_write_intmax(v->perms);
|
||
dbio_write_intmax(v->prep);
|
||
}
|
||
|
||
static int
|
||
read_propdef(Propdef *p)
|
||
{
|
||
const char *name;
|
||
return dbio_read_string_intern(&name) &&
|
||
((*p = dbpriv_new_propdef(name)), 1);
|
||
}
|
||
|
||
static void
|
||
write_propdef(Propdef * p)
|
||
{
|
||
dbio_write_string(p->name);
|
||
}
|
||
|
||
static int
|
||
read_propval(Pval * p)
|
||
{
|
||
return
|
||
dbio_read_var(&p->var) &&
|
||
dbio_read_objid(&p->owner) &&
|
||
dbio_read_uint16(&p->perms);
|
||
}
|
||
|
||
static void
|
||
write_propval(Pval * p)
|
||
{
|
||
dbio_write_var(p->var);
|
||
dbio_write_objid(p->owner);
|
||
dbio_write_intmax(p->perms);
|
||
}
|
||
|
||
|
||
/*********** Object I/O ***********/
|
||
|
||
static int
|
||
read_object(void)
|
||
{
|
||
Objid oid;
|
||
Object *o;
|
||
|
||
int sc = dbio_scxnf("#%"SCNdN"\v recycled", &oid);
|
||
if (!sc) {
|
||
errlog("READ_OBJECT: Bad first line\n");
|
||
return 0;
|
||
}
|
||
if (oid != db_last_used_objid() + 1) {
|
||
errlog("READ_OBJECT: Object number is out of order (expecting 1+#%"PRIdN")\n",
|
||
db_last_used_objid());
|
||
return 0;
|
||
}
|
||
|
||
if (sc == 2) {
|
||
dbpriv_new_recycled_object();
|
||
return 1;
|
||
}
|
||
|
||
/* Every 'return 0' from here to the end of this function
|
||
* should arguably be jumping to or calling a routine that
|
||
* frees this partly-created object, but since
|
||
* returning 0 from here does
|
||
* an immediate return 0 from read_db_file() which does
|
||
* an immediate return 0 from db_load() which does
|
||
* an immediate exit(1),
|
||
* it is not clear there would be any point --wrog
|
||
*/
|
||
o = dbpriv_new_object();
|
||
|
||
intmax_t i;
|
||
intmax_t nverbdefs;
|
||
if (!(dbio_read_string_intern(&o->name) &&
|
||
dbio_skip_lines(1, "READ_OBJECT") &&
|
||
/* discard old handles string */
|
||
|
||
dbio_read_uint16(&o->flags) &&
|
||
dbio_read_objid(&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_intmax(&nverbdefs)))
|
||
return 0;
|
||
|
||
Verbdef **prevv = &(o->verbdefs);
|
||
*prevv = NULL;
|
||
for (i = nverbdefs; i > 0; --i) {
|
||
Verbdef *v = mymalloc(sizeof(Verbdef), M_VERBDEF);
|
||
if (!read_verbdef(v))
|
||
return 0;
|
||
*prevv = v;
|
||
prevv = &(v->next);
|
||
}
|
||
|
||
|
||
intmax_t npropdefs;
|
||
if (!dbio_read_intmax(&npropdefs))
|
||
return 0;
|
||
|
||
o->propdefs.cur_length = npropdefs;
|
||
o->propdefs.max_length = npropdefs;
|
||
o->propdefs.l = NULL;
|
||
if (npropdefs != 0) {
|
||
o->propdefs.l = mymalloc(npropdefs * sizeof(Propdef), M_PROPDEF);
|
||
for (i = 0; i < npropdefs; i++)
|
||
if (!read_propdef(&o->propdefs.l[i]))
|
||
return 0;
|
||
}
|
||
|
||
intmax_t nprops;
|
||
if (!dbio_read_intmax(&nprops))
|
||
return 0;
|
||
|
||
if (nprops)
|
||
o->propval = mymalloc(nprops * sizeof(Pval), M_PVAL);
|
||
else
|
||
o->propval = 0;
|
||
|
||
for (i = 0; i < nprops; i++) {
|
||
if (!read_propval(o->propval + i))
|
||
return 0;
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
static void
|
||
write_object(Objid oid)
|
||
{
|
||
Object *o;
|
||
Verbdef *v;
|
||
int i;
|
||
int nverbdefs, nprops;
|
||
|
||
if (!valid(oid)) {
|
||
dbio_printf("#%"PRIdN" recycled\n", oid);
|
||
return;
|
||
}
|
||
o = dbpriv_find_object(oid);
|
||
|
||
dbio_printf("#%"PRIdN"\n", oid);
|
||
dbio_write_string(o->name);
|
||
dbio_write_string(""); /* placeholder for old handles string */
|
||
dbio_write_intmax(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_intmax(nverbdefs);
|
||
for (v = o->verbdefs; v; v = v->next)
|
||
write_verbdef(v);
|
||
|
||
dbio_write_intmax(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_intmax(nprops);
|
||
for (i = 0; i < nprops; i++)
|
||
write_propval(o->propval + i);
|
||
}
|
||
|
||
|
||
/*********** File-IO Hooks ***********/
|
||
|
||
#define DB_HOOK_TABLE_SIZE 5
|
||
|
||
static
|
||
struct db_file_hook {
|
||
db_before_hook before;
|
||
db_after_hook after;
|
||
int seq;
|
||
const char *msg;
|
||
}
|
||
load_hooks[DB_HOOK_TABLE_SIZE],
|
||
save_hooks[DB_HOOK_TABLE_SIZE];
|
||
|
||
static unsigned
|
||
next_load = 0,
|
||
next_save = 0,
|
||
hooks_initialized = 0;
|
||
|
||
static void
|
||
register_db_hooks(struct db_file_hook hooks[], unsigned *next,
|
||
int seq, db_before_hook before, db_after_hook after,
|
||
const char *message)
|
||
{
|
||
if (*next >= DB_HOOK_TABLE_SIZE)
|
||
panic("need to increase DB_HOOK_TABLE_SIZE in " __FILE__);
|
||
if (hooks_initialized)
|
||
panic("register_db_hooks() called too late");
|
||
|
||
struct db_file_hook *dfh = hooks + *next;
|
||
dfh->seq = seq;
|
||
dfh->before = before;
|
||
dfh->after = after;
|
||
dfh->msg = message;
|
||
++*next;
|
||
}
|
||
|
||
static void
|
||
db_run_before_hooks(struct db_file_hook hooks[], unsigned next,
|
||
const char *which)
|
||
{
|
||
unsigned i;
|
||
for (i = 0; i < next; ++i)
|
||
if (hooks[i].before) {
|
||
oklog("BEFORE(%s): %s\n", which, hooks[i].msg);
|
||
hooks[i].before();
|
||
}
|
||
}
|
||
|
||
static void
|
||
db_run_after_hooks(struct db_file_hook hooks[], unsigned next,
|
||
const char *which, int success)
|
||
{
|
||
unsigned i = next;
|
||
while (i--)
|
||
if (hooks[i].after) {
|
||
oklog("AFTER(%s): %s\n", which, hooks[i].msg);
|
||
hooks[i].after(success);
|
||
}
|
||
}
|
||
|
||
/* yes, I hate writing things twice; how could you tell? */
|
||
#define DO_LOADSAVE_(DO) DO(load) DO(save)
|
||
|
||
#define FNS_(load) \
|
||
void \
|
||
register_db_##load##_hooks(int seq, \
|
||
db_before_hook before, \
|
||
db_after_hook after, \
|
||
const char *message) \
|
||
{ \
|
||
register_db_hooks(load##_hooks, &next_##load, \
|
||
seq, before, after, message); \
|
||
} \
|
||
\
|
||
static inline void \
|
||
db_run_before_##load##_hooks(void) \
|
||
{ \
|
||
db_run_before_hooks(load##_hooks, next_##load, #load); \
|
||
} \
|
||
\
|
||
static inline void \
|
||
db_run_after_##load##_hooks(int success) \
|
||
{ \
|
||
db_run_after_hooks(load##_hooks, next_##load, \
|
||
#load, success); \
|
||
} \
|
||
|
||
DO_LOADSAVE_(FNS_)
|
||
#undef FNS_
|
||
|
||
static int
|
||
dfh_compare(const void *dfh_a, const void *dfh_b)
|
||
{
|
||
const struct db_file_hook *a = dfh_a;
|
||
const struct db_file_hook *b = dfh_b;
|
||
|
||
return a->seq < b->seq ? -1 : (a->seq == b->seq ? 0 : 1);
|
||
}
|
||
|
||
void
|
||
db_init_hooks(void)
|
||
{
|
||
if (hooks_initialized)
|
||
panic("db_init_hooks() called twice?");
|
||
++hooks_initialized;
|
||
|
||
# define QS_(load) \
|
||
qsort(load##_hooks, next_##load, \
|
||
sizeof(struct db_file_hook), \
|
||
dfh_compare); \
|
||
|
||
DO_LOADSAVE_(QS_)
|
||
# undef QS_
|
||
}
|
||
|
||
#undef DO_LOADSAVE_
|
||
|
||
/*********** File-level Input ***********/
|
||
|
||
static int
|
||
validate_hierarchies(void)
|
||
{
|
||
Objid oid;
|
||
Objid size = db_last_used_objid() + 1;
|
||
int broken = 0;
|
||
int fixed_nexts = 0;
|
||
|
||
oklog("VALIDATING the object hierarchies ...\n");
|
||
|
||
# define MAYBE_LOG_PROGRESS \
|
||
{ \
|
||
if (log_report_progress()) { \
|
||
oklog("VALIDATE: Done through #%"PRIdN" ...\n", oid); \
|
||
} \
|
||
}
|
||
|
||
oklog("VALIDATE: Phase 1: Check for invalid objects ...\n");
|
||
for (oid = 0; 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: #%"PRIdN".%s = #%"PRIdN" <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; oid < size; oid++) {
|
||
Object *o = dbpriv_find_object(oid);
|
||
|
||
MAYBE_LOG_PROGRESS;
|
||
if (o) {
|
||
# define CHECK(start, field, name) \
|
||
{ \
|
||
Objid slower = start; \
|
||
Objid faster = slower; \
|
||
while (faster != NOTHING) { \
|
||
faster = dbpriv_find_object(faster)->field; \
|
||
if (faster == NOTHING) \
|
||
break; \
|
||
faster = dbpriv_find_object(faster)->field; \
|
||
slower = dbpriv_find_object(slower)->field; \
|
||
if (faster == slower) { \
|
||
errlog("VALIDATE: Cycle in `%s' chain of #%"PRIdN"\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
|
||
|
||
/* setup for phase 3: set two temp flags on every object */
|
||
o->flags |= (3<<FLAG_FIRST_TEMP);
|
||
}
|
||
}
|
||
|
||
if (broken) /* Can't continue if cycles found */
|
||
return 0;
|
||
|
||
oklog("VALIDATE: Phase 3a: Finding delusional parents ...\n");
|
||
for (oid = 0; oid < size; oid++) {
|
||
Object *o = dbpriv_find_object(oid);
|
||
|
||
MAYBE_LOG_PROGRESS;
|
||
if (o) {
|
||
# define CHECK(up, down, down_name, across, FLAG) \
|
||
{ \
|
||
Objid oidkid; \
|
||
Object *okid; \
|
||
\
|
||
for (oidkid = o->down; \
|
||
oidkid != NOTHING; \
|
||
oidkid = okid->across) { \
|
||
\
|
||
okid = dbpriv_find_object(oidkid); \
|
||
if (okid->up != oid) { \
|
||
errlog( \
|
||
"VALIDATE: #%"PRIdN" erroneously on #%"PRIdN"'s %s list.\n", \
|
||
oidkid, oid, down_name); \
|
||
broken = 1; \
|
||
} \
|
||
else { \
|
||
/* mark okid as properly claimed */ \
|
||
okid->flags &= ~(1<<(FLAG)); \
|
||
} \
|
||
} \
|
||
}
|
||
|
||
CHECK(parent, child, "child", sibling, FLAG_FIRST_TEMP);
|
||
CHECK(location, contents, "contents", next, FLAG_FIRST_TEMP+1);
|
||
|
||
# undef CHECK
|
||
}
|
||
}
|
||
|
||
oklog("VALIDATE: Phase 3b: Finding delusional children ...\n");
|
||
for (oid = 0; oid < size; oid++) {
|
||
Object *o = dbpriv_find_object(oid);
|
||
|
||
MAYBE_LOG_PROGRESS;
|
||
if (o) {
|
||
# define CHECK(up, up_name, down_name, FLAG) \
|
||
{ \
|
||
/* If oid is unclaimed, up must be NOTHING */ \
|
||
if ((o->flags & (1<<(FLAG))) && o->up != NOTHING) { \
|
||
errlog("VALIDATE: #%"PRIdN" not in %s (#%"PRIdN")'s %s list.\n", \
|
||
oid, up_name, o->up, down_name); \
|
||
broken = 1; \
|
||
} \
|
||
}
|
||
|
||
CHECK(parent, "parent", "child", FLAG_FIRST_TEMP);
|
||
CHECK(location, "location", "contents", FLAG_FIRST_TEMP+1);
|
||
|
||
/* clear temp flags */
|
||
o->flags &= ~(3<<FLAG_FIRST_TEMP);
|
||
|
||
# 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, "#%"PRIdN":%s", db_verb_definer(*h), db_verb_names(*h));
|
||
return reset_stream(s);
|
||
}
|
||
|
||
static int
|
||
read_db_file(void)
|
||
{
|
||
/* Evidently, prehistory DBs had no header line, they would just
|
||
* go straight to the object count. Therefore, a prehistory DB
|
||
* will not start with '*'. Since stdio allows us to put back
|
||
* one character, we can do a quick probe. I prefer this to
|
||
* further messing with dbio_scxnf. --wrog
|
||
*/
|
||
int first_byte = dbio_peek_byte();
|
||
if (header_format_string[0] != first_byte) {
|
||
dbio_input_version = DBV_Prehistory;
|
||
}
|
||
else if (!dbio_scxnf(header_format_string, &dbio_input_version)) {
|
||
errlog("READ_DB_FILE: Bad DB header (no version?)\n");
|
||
return 0;
|
||
}
|
||
else if (!check_db_version(dbio_input_version)) {
|
||
errlog("READ_DB_FILE: Unknown DB version number: %u\n",
|
||
dbio_input_version);
|
||
return 0;
|
||
}
|
||
|
||
UNum i,
|
||
nobjs = 0,
|
||
nprogs = 0,
|
||
nusers = 0;
|
||
|
||
if (first_byte != EOF &&
|
||
!dbio_scxnf("%"SCNuN"\n%"SCNuN"\n%*d\n%"SCNuN,
|
||
&nobjs, &nprogs, &nusers)) {
|
||
errlog("READ_DB_FILE: Bad DB header (missing counts?)\n");
|
||
return 0;
|
||
}
|
||
|
||
Var user_list = new_list(nusers);
|
||
for (i = 1; i <= nusers; i++) {
|
||
user_list.v.list[i].type = TYPE_OBJ;
|
||
if (!dbio_read_objid(&user_list.v.list[i].v.obj))
|
||
return 0;
|
||
}
|
||
dbpriv_set_all_users(user_list);
|
||
|
||
oklog("LOADING: Reading %"PRIdN" objects...\n", nobjs);
|
||
for (i = 1; i <= nobjs; i++) {
|
||
if (!read_object()) {
|
||
errlog("READ_DB_FILE: Bad object #%"PRIdN".\n", i - 1);
|
||
return 0;
|
||
}
|
||
if (i == nobjs || log_report_progress())
|
||
oklog("LOADING: Done reading %"PRIdN" objects ...\n", i);
|
||
}
|
||
|
||
if (!validate_hierarchies()) {
|
||
errlog("READ_DB_FILE: Errors in object hierarchies.\n");
|
||
return 0;
|
||
}
|
||
oklog("LOADING: Reading %"PRIdN" MOO verb programs...\n", nprogs);
|
||
for (i = 1; i <= nprogs; i++) {
|
||
Objid oid;
|
||
UNum vnum;
|
||
if (!dbio_scxnf("#%"SCNdN":%"SCNdN, &oid, &vnum)) {
|
||
errlog("READ_DB_FILE: Bad program header, i = %"PRIdN".\n", i);
|
||
return 0;
|
||
}
|
||
if (!valid(oid)) {
|
||
errlog("READ_DB_FILE: Verb for non-existant object: #%"PRIdN":%"PRIdN".\n",
|
||
oid, vnum);
|
||
return 0;
|
||
}
|
||
|
||
db_verb_handle h = db_find_indexed_verb(oid, vnum + 1); /* DB file is 0-based. */
|
||
if (!h.ptr) {
|
||
errlog("READ_DB_FILE: Unknown verb index: #%"PRIdN":%"PRIdN".\n", oid, vnum);
|
||
return 0;
|
||
}
|
||
|
||
Program *program = dbio_read_program(dbio_input_version, fmt_verb_name, &h);
|
||
if (!program) {
|
||
errlog("READ_DB_FILE: Unparsable program #%"PRIdN":%"PRIdN".\n", oid, vnum);
|
||
return 0;
|
||
}
|
||
db_set_verb_program(h, program);
|
||
if (i == nprogs || log_report_progress())
|
||
oklog("LOADING: Done reading %"PRIdN" verb programs...\n", i);
|
||
}
|
||
|
||
oklog("LOADING: Reading forked and suspended tasks...\n");
|
||
if (first_byte != EOF && !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;
|
||
}
|
||
dbpriv_dbio_input_finished();
|
||
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;
|
||
|
||
db_run_before_save_hooks();
|
||
|
||
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_db_version);
|
||
dbio_printf("\n%"PRIdN"\n%d\n%d\n%"PRIdN"\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 %"PRIdN" objects...\n", reason, max_oid + 1);
|
||
for (oid = 0; oid <= max_oid; oid++) {
|
||
write_object(oid);
|
||
if (oid == max_oid || log_report_progress())
|
||
oklog("%s: Done writing %"PRIdN" 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("#%"PRIdN":%d\n", oid, vcount);
|
||
dbio_write_program(v->program);
|
||
if (++i == nprogs || log_report_progress())
|
||
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;
|
||
|
||
db_run_after_save_hooks(success);
|
||
|
||
dbpriv_dbio_output_finished();
|
||
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 {
|
||
fflush(f);
|
||
fsync(fileno(f));
|
||
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);
|
||
|
||
db_run_before_load_hooks();
|
||
|
||
oklog("LOADING: %s\n", input_db_name);
|
||
if (!read_db_file()) {
|
||
/* XXX is there any point to this? */
|
||
db_run_after_load_hooks(0);
|
||
|
||
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);
|
||
|
||
db_run_after_load_hooks(1);
|
||
|
||
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;
|
||
}
|
||
|
||
int64_t
|
||
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(void)
|
||
{
|
||
dump_database(DUMP_SHUTDOWN);
|
||
|
||
free_str(input_db_name);
|
||
free_str(dump_db_name);
|
||
}
|
||
|
||
|
||
/*
|
||
* $Log$
|
||
* 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
|
||
*/
|