root/1.8.3/tags/p3/src/db.c

Revision 919, 53.4 KB (checked in by shawnw, 19 months ago)

1.8.3p3

Line 
1/**
2 * \file db.c
3 *
4 * \brief Loading and saving the PennMUSH object database.
5 *
6 *
7 */
8
9#include "copyrite.h"
10#include "config.h"
11
12#include <stdio.h>
13#include <ctype.h>
14#include <string.h>
15#ifdef I_SYS_TIME
16#include <sys/time.h>
17#ifdef TIME_WITH_SYS_TIME
18#include <time.h>
19#endif
20#else
21#include <time.h>
22#endif
23#include <stdlib.h>
24#include "conf.h"
25#include "dbio.h"
26#include "externs.h"
27#include "mushdb.h"
28#include "attrib.h"
29#include "mymalloc.h"
30#include "game.h"
31#include "flags.h"
32#include "lock.h"
33#include "dbdefs.h"
34#include "log.h"
35#include "strtree.h"
36#include "parse.h"
37#include "privtab.h"
38#include "htab.h"
39#include "extmail.h"
40#include "confmagic.h"
41#include "ansi.h"
42
43#ifdef WIN32
44#pragma warning( disable : 4761)        /* disable warning re conversion */
45#endif
46
47#ifdef WIN32SERVICES
48void shutdown_checkpoint(void);
49#endif
50
51/** Get a ref out of the database if a given db flag is set */
52#define MAYBE_GET(f,x) \
53        (globals.indb_flags & (x)) ? getref(f) : 0
54
55
56int loading_db = 0;   /**< Are we loading the database? */
57
58char db_timestamp[100]; /**< Time the read database was saved. */
59
60struct object *db = NULL; /**< The object db array */
61dbref db_top = 0;         /**< The number of objects in the db array */
62
63dbref errobj;             /**< Dbref of object on which an error has occurred */
64
65int dbline = 0;           /**< Line of the database file being read */
66
67/** String that markes the end of dumps */
68const char *EOD = "***END OF DUMP***\n";
69
70#ifndef DB_INITIAL_SIZE
71#define DB_INITIAL_SIZE 5000   /**< Initial size for db array */
72#endif                          /* DB_INITIAL_SIZE */
73
74dbref db_size = DB_INITIAL_SIZE;  /**< Current size of db array */
75
76HASHTAB htab_objdata;         /**< Object data hash table */
77HASHTAB htab_objdata_keys;    /**< Object data keys hash table */
78
79static void db_grow(dbref newtop);
80
81static void db_write_obj_basic(FILE * f, dbref i, struct object *o);
82int db_paranoid_write_object(FILE * f, dbref i, int flag);
83int db_write_object(FILE * f, dbref i);
84void putlocks(FILE * f, lock_list *l);
85void getlocks(dbref i, FILE * f);
86void get_new_locks(dbref i, FILE * f, int c);
87void db_read_attrs(FILE * f, dbref i, int c);
88int get_list(FILE * f, dbref i);
89void db_free(void);
90static void init_objdata_htab(int size, void (*free_data) (void *));
91static void db_write_flags(FILE * f);
92static dbref db_read_oldstyle(FILE * f);
93
94StrTree object_names;       /**< String tree of object names */
95extern StrTree atr_names;
96
97void init_names(void);
98
99void create_minimal_db(void);
100
101extern struct db_stat_info current_state;
102
103/** Initialize the name strtree.
104 */
105void
106init_names(void)
107{
108  st_init(&object_names);
109}
110
111/** Set an object's name through the name strtree.
112 * We maintain object names in a strtree because many objects have
113 * the same name (cardinal exits, weapons and armor, etc.)
114 * This function is used to set an object's name; if the name's already
115 * in the strtree, we just get a pointer to it, saving memory.
116 * (If not, we add it to the strtree and use that pointer).
117 * \param obj dbref of object whose name is to be set.
118 * \param newname name to set on the object, or NULL to clear the name.
119 * \return object's new name, or NULL if none is given.
120 */
121const char *
122set_name(dbref obj, const char *newname)
123{
124  /* if pointer not null unalloc it */
125  if (Name(obj))
126    st_delete(Name(obj), &object_names);
127  if (!newname || !*newname)
128    return NULL;
129  Name(obj) = st_insert(newname, &object_names);
130  return Name(obj);
131}
132
133int db_init = 0;  /**< Has the db array been initialized yet? */
134
135static void
136db_grow(dbref newtop)
137{
138  struct object *newdb;
139  dbref initialized;
140  struct object *o;
141
142  if (newtop > db_top) {
143    initialized = db_top;
144    current_state.total = newtop;
145    current_state.garbage += newtop - db_top;
146    db_top = newtop;
147    if (!db) {
148      /* make the initial one */
149      db_size = (db_init) ? db_init : DB_INITIAL_SIZE;
150      while (db_top > db_size)
151        db_size *= 2;
152      if ((db = (struct object *)
153           malloc(db_size * sizeof(struct object))) == NULL) {
154        do_rawlog(LT_ERR, "ERROR: out of memory while creating database!");
155        abort();
156      }
157    }
158    /* maybe grow it */
159    if (db_top > db_size) {
160      /* make sure it's big enough */
161      while (db_top > db_size)
162        db_size *= 2;
163      if ((newdb = (struct object *)
164           realloc(db, db_size * sizeof(struct object))) == NULL) {
165        do_rawlog(LT_ERR, "ERROR: out of memory while extending database!");
166        abort();
167      }
168      db = newdb;
169    }
170    while (initialized < db_top) {
171      o = db + initialized;
172      o->name = 0;
173      o->list = 0;
174      o->location = NOTHING;
175      o->contents = NOTHING;
176      o->exits = NOTHING;
177      o->next = NOTHING;
178      o->parent = NOTHING;
179      o->locks = NULL;
180      o->owner = GOD;
181      o->zone = NOTHING;
182      o->penn = 0;
183      o->type = TYPE_GARBAGE;
184      o->flags = NULL;
185      o->powers = NULL;
186      o->warnings = 0;
187      o->modification_time = o->creation_time = mudtime;
188      o->attrcount = 0;
189      initialized++;
190    }
191  }
192}
193
194/** Allocate a new object structure.
195 * This function allocates and returns a new object structure.
196 * The caller must see that it gets appropriately typed and otherwise
197 * initialized.
198 * \return dbref of newly allocated object.
199 */
200dbref
201new_object(void)
202{
203  dbref newobj;
204  struct object *o;
205  /* if stuff in free list use it */
206  if ((newobj = free_get()) == NOTHING) {
207    /* allocate more space */
208    newobj = db_top;
209    db_grow(db_top + 1);
210  }
211  /* clear it out */
212  o = db + newobj;
213  o->name = 0;
214  o->list = 0;
215  o->location = NOTHING;
216  o->contents = NOTHING;
217  o->exits = NOTHING;
218  o->next = NOTHING;
219  o->parent = NOTHING;
220  o->locks = NULL;
221  o->owner = GOD;
222  o->zone = NOTHING;
223  o->penn = 0;
224  o->type = TYPE_GARBAGE;
225  o->flags = new_flag_bitmask("FLAG");
226  o->powers = new_flag_bitmask("POWER");
227  o->warnings = 0;
228  o->modification_time = o->creation_time = mudtime;
229  o->attrcount = 0;
230  if (current_state.garbage)
231    current_state.garbage--;
232  return newobj;
233}
234
235/** Output a long int to a file.
236 * \param f file pointer to write to.
237 * \param ref value to write.
238 */
239void
240putref(FILE * f, long int ref)
241{
242  OUTPUT(fprintf(f, "%ld\n", ref));
243}
244
245/** Output a string to a file.
246 * This function writes a string to a file, double-quoted,
247 * appropriately escaping quotes and backslashes (the escape character).
248 * \param f file pointer to write to.
249 * \param s value to write.
250 */
251void
252putstring(FILE * f, const char *s)
253{
254  OUTPUT(putc('"', f));
255  while (*s) {
256    switch (*s) {
257    case '\\':
258    case '"':
259      OUTPUT(putc('\\', f));
260      /* FALL THROUGH */
261    default:
262      OUTPUT(putc(*s, f));
263    }
264    s++;
265  }
266  OUTPUT(putc('"', f));
267  OUTPUT(putc('\n', f));
268}
269
270/** Read a labeled entry from a database.
271 * Labeled entries look like 'label entry', and are used
272 * extensively in the current database format, and to a lesser
273 * extent in older versions.
274 * \param f the file to read from
275 * \param label pointer to update to the address of a static
276 * buffer containing the label that was read.
277 * \param value pointer to update to the address of a static
278 * buffer containing the value that was read.
279 */
280void
281db_read_labeled_string(FILE * f, char **label, char **value)
282{
283  static char lbuf[BUFFER_LEN], vbuf[BUFFER_LEN];
284  int c;
285  char *p;
286
287  *label = lbuf;
288  *value = vbuf;
289
290  /* invariant: we start at the beginning of a line. */
291
292  dbline++;
293
294  do {
295    c = getc(f);
296    while (isspace(c)) {
297      if (c == '\n')
298        dbline++;
299      c = getc(f);
300    }
301    if (c == '#') {
302      while ((c = getc(f)) != '\n' && c != EOF) {
303        /* nothing */
304      }
305      if (c == '\n')
306        dbline++;
307    }
308  } while (c != EOF && isspace(c));
309
310  if (c == EOF) {
311    do_rawlog(LT_ERR, T("DB: Unexpected EOF at line %d"), dbline);
312    longjmp(db_err, 1);
313  }
314
315  /* invariant: we should have the first character of a label in 'c'. */
316
317  p = lbuf;
318  do {
319    if (c != '_' && c != '-' && c != '!' && c != '.' && c != '>' && c != '<' && c != '#' &&     /* these really should only be first time */
320        !isalnum(c)) {
321      do_rawlog(LT_ERR, "DB: Illegal character '%c'(%d) in label, line %d",
322                c, c, dbline);
323      longjmp(db_err, 1);
324    }
325    safe_chr(c, lbuf, &p);
326    c = getc(f);
327  } while (c != EOF && !isspace(c));
328  *p++ = '\0';
329  if (p >= lbuf + BUFFER_LEN)
330    do_rawlog(LT_ERR, "DB: warning: very long label, line %d", dbline);
331
332  /* suck up separating whitespace */
333  while (c != '\n' && c != EOF && isspace(c))
334    c = getc(f);
335
336  /* check for presence of a value, which we must have. */
337  if (c == EOF || c == '\n') {
338    if (c == EOF)
339      do_rawlog(LT_ERR, T("DB: Unexpected EOF at line %d"), dbline);
340    else
341      do_rawlog(LT_ERR, T("DB: Missing value for '%s' at line %d"), lbuf,
342                dbline);
343    longjmp(db_err, 1);
344  }
345
346  /* invariant: we should have the first character of a value in 'c'. */
347
348  p = vbuf;
349  if (c == '"') {
350    /* quoted string */
351    int sline;
352    sline = dbline;
353    for (;;) {
354      c = getc(f);
355      if (c == '"')
356        break;
357      if (c == '\\')
358        c = getc(f);
359      if (c == EOF) {
360        do_rawlog(LT_ERR, "DB: Unclosed quoted string starting on line %d",
361                  sline);
362        longjmp(db_err, 1);
363      }
364      if (c == '\0')
365        do_rawlog(LT_ERR,
366                  "DB: warning: null in quoted string, remainder lost, line %d",
367                  dbline);
368      if (c == '\n')
369        dbline++;
370      safe_chr(c, vbuf, &p);
371    }
372    do {
373      c = getc(f);
374      if (c != EOF && !isspace(c)) {
375        do_rawlog(LT_ERR, "DB: Garbage after quoted string, line %d", dbline);
376        longjmp(db_err, 1);
377      }
378    } while (c != '\n' && c != EOF);
379  } else {
380    /* non-quoted value */
381    do {
382      if (c != '_' && c != '-' && c != '!' && c != '.' &&
383          c != '#' && !isalnum(c) && !isspace(c)) {
384        do_rawlog(LT_ERR, "DB: Illegal character '%c'(%d) in value, line %d",
385                  c, c, dbline);
386        longjmp(db_err, 1);
387      }
388      safe_chr(c, vbuf, &p);
389      c = getc(f);
390    } while (c != EOF && c != '\n');
391    if (c == '\n' && (p - vbuf >= 2) && (*(p - 2) == '\r')) {
392      /* Oops, we read in \r\n at the end of this value. Drop the \r */
393      p--;
394      *(p - 1) = '\n';
395    }
396  }
397  *p++ = '\0';
398  if (p >= vbuf + BUFFER_LEN)
399    do_rawlog(LT_ERR, "DB: warning: very long value, line %d", dbline);
400
401  /* note no line increment for final newline because of initial increment */
402}
403
404/** Read a string with a given label.
405 * If the label read is different than the one being checked, the
406 * database load will abort with an error.
407 * \param f the file to read from.
408 * \param label the label that should be read.
409 * \param value pointer to update to the address of a static
410 * buffer containing the value that was read.
411 */
412void
413db_read_this_labeled_string(FILE * f, const char *label, char **value)
414{
415  char *readlabel;
416
417  db_read_labeled_string(f, &readlabel, value);
418
419  if (strcmp(readlabel, label)) {
420    do_rawlog(LT_ERR,
421              T("DB: error: Got label '%s', expected label '%s' at line %d"),
422              readlabel, label, dbline);
423    longjmp(db_err, 1);
424  }
425}
426
427/** Read an int with a given label.
428 * If the label read is different than the one being checked, the
429 * database load will abort with an error.
430 * \param f the file to read from.
431 * \param label the label that should be read.
432 * \param value pointer to update to the number that was read.
433 */
434void
435db_read_this_labeled_int(FILE * f, const char *label, int *value)
436{
437  char *readlabel;
438  char *readvalue;
439
440  db_read_labeled_string(f, &readlabel, &readvalue);
441
442  if (strcmp(readlabel, label)) {
443    do_rawlog(LT_ERR,
444              T("DB: error: Got label '%s', expected label '%s' at line %d"),
445              readlabel, label, dbline);
446    longjmp(db_err, 1);
447  }
448
449  *value = parse_integer(readvalue);
450}
451
452/** Read an int and label.
453 * \param f the file to read from.
454 * \param label pointer to update to the address of a static
455 * buffer containing the label that was read.
456 * \param value pointer to update to the number that was read.
457 */
458void
459db_read_labeled_int(FILE * f, char **label, int *value)
460{
461  char *readvalue;
462  db_read_labeled_string(f, label, &readvalue);
463  *value = parse_integer(readvalue);
464}
465
466
467/** Read a uint32_t with a given label.
468 * If the label read is different than the one being checked, the
469 * database load will abort with an error.
470 * \param f the file to read from.
471 * \param label the label that should be read.
472 * \param value pointer to update to the number that was read.
473 */
474void
475db_read_this_labeled_uint32(FILE * f, const char *label, uint32_t * value)
476{
477  char *readlabel;
478  char *readvalue;
479
480  db_read_labeled_string(f, &readlabel, &readvalue);
481
482  if (strcmp(readlabel, label)) {
483    do_rawlog(LT_ERR,
484              T("DB: error: Got label '%s', expected label '%s' at line %d"),
485              readlabel, label, dbline);
486    longjmp(db_err, 1);
487  }
488
489  *value = parse_uint32(readvalue, NULL, 10);
490}
491
492/** Read a uint32_t and label.
493 * \param f the file to read from.
494 * \param label pointer to update to the address of a static
495 * buffer containing the label that was read.
496 * \param value pointer to update to the number that was read.
497 */
498void
499db_read_labeled_uint32(FILE * f, char **label, uint32_t * value)
500{
501  char *readvalue;
502  db_read_labeled_string(f, label, &readvalue);
503  *value = parse_uint32(readvalue, NULL, 10);
504}
505
506
507/** Read a dbref with a given label.
508 * If the label read is different than the one being checked, the
509 * database load will abort with an error.
510 * \param f the file to read from.
511 * \param label the label that should be read.
512 * \param value pointer to update to the dbref that was read.
513 */
514void
515db_read_this_labeled_dbref(FILE * f, const char *label, dbref *val)
516{
517  char *readlabel;
518  char *readvalue;
519
520  db_read_labeled_string(f, &readlabel, &readvalue);
521
522  if (strcmp(readlabel, label)) {
523    do_rawlog(LT_ERR,
524              T("DB: error: Got label '%s', expected label '%s' at line %d"),
525              readlabel, label, dbline);
526    longjmp(db_err, 1);
527  }
528  *val = qparse_dbref(readvalue);
529}
530
531/** Read a dbref and label.
532 * \param f the file to read from.
533 * \param label pointer to update to the address of a static
534 * buffer containing the label that was read.
535 * \param value pointer to update to the dbref that was read.
536 */
537void
538db_read_labeled_dbref(FILE * f, char **label, dbref *val)
539{
540  char *readvalue;
541  db_read_labeled_string(f, label, &readvalue);
542  *val = qparse_dbref(readvalue);
543}
544
545static void
546db_write_label(FILE * f, char const *l)
547{
548  OUTPUT(fputs(l, f));
549  OUTPUT(putc(' ', f));
550}
551
552void
553db_write_labeled_string(FILE * f, char const *label, char const *value)
554{
555  db_write_label(f, label);
556  putstring(f, value);
557}
558
559void
560db_write_labeled_int(FILE * f, char const *label, int value)
561{
562  OUTPUT(fprintf(f, "%s %d\n", label, value));
563}
564
565void
566db_write_labeled_dbref(FILE * f, char const *label, dbref value)
567{
568  OUTPUT(fprintf(f, "%s #%d\n", label, value));
569}
570
571/** Write a boolexp to a file in unparsed (text) form.
572 * \param f file pointer to write to.
573 * \param b pointer to boolexp to write.
574 */
575void
576putboolexp(FILE * f, boolexp b)
577{
578  db_write_labeled_string(f, "  key", unparse_boolexp(GOD, b, UB_DBREF));
579}
580
581/** Write a list of locks to a file.
582 * \param f file pointer to write to.
583 * \param l pointer to lock_list to write.
584 */
585void
586putlocks(FILE * f, lock_list *l)
587{
588  lock_list *ll;
589  int count = 0;
590  for (ll = l; ll; ll = ll->next)
591    count++;
592  db_write_labeled_int(f, "lockcount", count);
593  for (ll = l; ll; ll = ll->next) {
594    db_write_labeled_string(f, " type", ll->type);
595    db_write_labeled_dbref(f, "  creator", L_CREATOR(ll));
596    db_write_labeled_string(f, "  flags", lock_flags_long(ll));
597    db_write_labeled_int(f, "  derefs", chunk_derefs(L_KEY(ll)));
598    putboolexp(f, ll->key);
599    /* putboolexp adds a '\n', so we won't. */
600  }
601}
602
603
604/** Write out the basics of an object.
605 * This function writes out the basic information associated with an
606 * object - just about everything but the attributes.
607 * \param f file pointer to write to.
608 * \param i dbref of object to write.
609 * \param o pointer to object to write.
610 */
611static void
612db_write_obj_basic(FILE * f, dbref i, struct object *o)
613{
614  db_write_labeled_string(f, "name", o->name);
615  db_write_labeled_dbref(f, "location", o->location);
616  db_write_labeled_dbref(f, "contents", o->contents);
617  db_write_labeled_dbref(f, "exits", o->exits);
618  db_write_labeled_dbref(f, "next", o->next);
619  db_write_labeled_dbref(f, "parent", o->parent);
620  putlocks(f, Locks(i));
621  db_write_labeled_dbref(f, "owner", o->owner);
622  db_write_labeled_dbref(f, "zone", o->zone);
623  db_write_labeled_int(f, "pennies", Pennies(i));
624  db_write_labeled_int(f, "type", Typeof(i));
625  db_write_labeled_string(f, "flags",
626                          bits_to_string("FLAG", o->flags, GOD, NOTHING));
627  db_write_labeled_string(f, "powers",
628                          bits_to_string("POWER", o->powers, GOD, NOTHING));
629  db_write_labeled_string(f, "warnings", unparse_warnings(o->warnings));
630  db_write_labeled_int(f, "created", (int) o->creation_time);
631  db_write_labeled_int(f, "modified", (int) o->modification_time);
632}
633
634/** Write out an object.
635 * This function writes a single object out to a file.
636 * \param f file pointer to write to.
637 * \param i dbref of object to write.
638 */
639int
640db_write_object(FILE * f, dbref i)
641{
642  struct object *o;
643  ALIST *list;
644  int count = 0;
645
646  o = db + i;
647  db_write_obj_basic(f, i, o);
648
649  /* write the attribute list */
650
651  /* Don't trust AttrCount(thing) for number of attributes to write. */
652  for (list = o->list; list; list = AL_NEXT(list)) {
653    if (AF_Nodump(list))
654      continue;
655    count++;
656  }
657  db_write_labeled_int(f, "attrcount", count);
658
659  for (list = o->list; list; list = AL_NEXT(list)) {
660    if (AF_Nodump(list))
661      continue;
662    db_write_labeled_string(f, " name", AL_NAME(list));
663    db_write_labeled_dbref(f, "  owner", Owner(AL_CREATOR(list)));
664    db_write_labeled_string(f, "  flags", atrflag_to_string(AL_FLAGS(list)));
665    db_write_labeled_int(f, "  derefs", AL_DEREFS(list));
666    db_write_labeled_string(f, "  value", atr_value(list));
667  }
668  return 0;
669}
670
671/** Write out the object database to disk.
672 * \verbatim
673 * This function writes the databsae out to disk. The database
674 * structure currently looks something like this:
675 * +V<header line>
676 * +FLAGS LIST
677 * <flag data>
678 * +POWERS LIST
679 * <flag data>
680 * ~<number of objects>
681 * <object data>
682 * \endverbatim
683 * \param f file pointer to write to.
684 * \param flag 0 for normal dump, DBF_PANIC for panic dumps.
685 * \return the number of objects in the database (db_top)
686 */
687dbref
688db_write(FILE * f, int flag)
689{
690  dbref i;
691  int dbflag;
692
693  /* print a header line to make a later conversion to 2.0 easier to do.
694   * the odd choice of numbers is based on 256*x + 2 offset
695   * The original PennMUSH had x=5 (chat) or x=6 (nochat), and Tiny expects
696   * to deal with that. We need to use some extra flags as well, so
697   * we may be adding to 5/6 as needed, using successive binary numbers.
698   */
699  dbflag = 5 + flag;
700  dbflag += DBF_NO_CHAT_SYSTEM;
701  dbflag += DBF_WARNINGS;
702  dbflag += DBF_CREATION_TIMES;
703  dbflag += DBF_SPIFFY_LOCKS;
704  dbflag += DBF_NEW_STRINGS;
705  dbflag += DBF_TYPE_GARBAGE;
706  dbflag += DBF_SPLIT_IMMORTAL;
707  dbflag += DBF_NO_TEMPLE;
708  dbflag += DBF_LESS_GARBAGE;
709  dbflag += DBF_AF_VISUAL;
710  dbflag += DBF_VALUE_IS_COST;
711  dbflag += DBF_LINK_ANYWHERE;
712  dbflag += DBF_NO_STARTUP_FLAG;
713  dbflag += DBF_AF_NODUMP;
714  dbflag += DBF_NEW_FLAGS;
715  dbflag += DBF_NEW_POWERS;
716  dbflag += DBF_POWERS_LOGGED;
717  dbflag += DBF_LABELS;
718  dbflag += DBF_SPIFFY_AF_ANSI;
719
720  OUTPUT(fprintf(f, "+V%d\n", dbflag * 256 + 2));
721
722  db_write_labeled_string(f, "savedtime", show_time(mudtime, 1));
723
724  db_write_flags(f);
725
726  OUTPUT(fprintf(f, "~%d\n", db_top));
727
728  for (i = 0; i < db_top; i++) {
729#ifdef WIN32SERVICES
730    /* Keep the service manager happy */
731    if (shutdown_flag && (i & 0xFF) == 0)
732      shutdown_checkpoint();
733#endif
734    if (IsGarbage(i))
735      continue;
736    OUTPUT(fprintf(f, "!%d\n", i));
737    db_write_object(f, i);
738  }
739  OUTPUT(fputs(EOD, f));
740  return db_top;
741}
742
743static void
744db_write_flags(FILE * f)
745{
746  OUTPUT(fprintf(f, "+FLAGS LIST\n"));
747  flag_write_all(f, "FLAG");
748  OUTPUT(fprintf(f, "+POWER LIST\n"));
749  flag_write_all(f, "POWER");
750}
751
752
753/** Write out an object, in paranoid fashion.
754 * This function writes a single object out to a file in paranoid
755 * mode, which warns about several potential types of corruption,
756 * and can fix some of them.
757 * \param f file pointer to write to.
758 * \param i dbref of object to write.
759 * \param flag 1 = debug, 0 = normal
760 */
761int
762db_paranoid_write_object(FILE * f, dbref i, int flag)
763{
764  struct object *o;
765  ALIST *list, *next;
766  char name[BUFFER_LEN];
767  char tbuf1[BUFFER_LEN];
768  int err = 0;
769  char *p;
770  char lastp;
771  dbref owner;
772  int fixmemdb = 0;
773  int count = 0;
774  int attrcount = 0;
775
776  o = db + i;
777  db_write_obj_basic(f, i, o);
778  /* fflush(f); */
779
780  /* write the attribute list, scanning */
781  for (list = o->list; list; list = AL_NEXT(list)) {
782    if (AF_Nodump(list))
783      continue;
784    attrcount++;
785  }
786
787  db_write_labeled_int(f, "attrcount", attrcount);
788
789  for (list = o->list; list; list = next) {
790    next = AL_NEXT(list);
791    if (AF_Nodump(list))
792      continue;
793    fixmemdb = err = 0;
794    /* smash unprintable characters in the name, replace with ! */
795    strcpy(name, AL_NAME(list));
796    for (p = name; *p; p++) {
797      if (!isprint((unsigned char) *p) || isspace((unsigned char) *p)) {
798        *p = '!';
799        fixmemdb = err = 1;
800      }
801    }
802    if (err) {
803      /* If name already exists on this object, try adding a
804       * number to the end. Give up if we can't find one < 10000
805       */
806      if (atr_get_noparent(i, name)) {
807        count = 0;
808        do {
809          name[BUFFER_LEN - 6] = '\0';
810          snprintf(tbuf1, BUFFER_LEN, "%s%d", name, count);
811          count++;
812        } while (count < 10000 && atr_get_noparent(i, tbuf1));
813        mush_strncpy(name, tbuf1, BUFFER_LEN);
814      }
815      do_rawlog(LT_CHECK,
816                T(" * Bad attribute name on #%d. Changing name to %s.\n"),
817                i, name);
818      err = 0;
819    }
820    /* check the owner */
821    owner = AL_CREATOR(list);
822    if (!GoodObject(owner)) {
823      do_rawlog(LT_CHECK, T(" * Bad owner on attribute %s on #%d.\n"), name, i);
824      owner = GOD;
825      fixmemdb = 1;
82