#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include "lint.h" #include "comm.h" #include "interpret.h" #include "object.h" #include "sent.h" #include "config.h" #include "wiz_list.h" #include "exec.h" extern int d_flag; #ifdef D_FLAG extern int D_flag; #endif extern int total_num_prog_blocks, total_prog_block_size; extern pid_t getpid(); void remove_swap_file PROT((struct object *)); struct object *previous_ob; extern struct svalue const0; extern int trace_level; int tot_alloc_object, tot_alloc_object_size; #if defined(MSDOS) || ( defined(atarist) && !defined(MINIX) ) #define MSDOS_FS #endif #define SAVE_OBJECT_VERSION '0' #define CURRENT_VERSION 0 #ifdef FLOAT_FORMAT_0 #define SAVE_OBJECT_HOST '0' #define CURRENT_HOST 0 #endif #ifdef FLOAT_FORMAT_1 #define SAVE_OBJECT_HOST '1' #define CURRENT_HOST 1 #endif #define SAVE_OBJECT_BUFSIZE 4096 #define MY_PUTC(char) {\ *buf_pnt++ = char;\ if (!-- buf_left) {\ buf_pnt = write_buffer();\ buf_left = SAVE_OBJECT_BUFSIZE;\ }\ } #define L_PUTC_PROLOG register char *l_buf_pnt = buf_pnt;\ register int l_buf_left = buf_left; #define L_PUTC(char) {\ *l_buf_pnt++ = char;\ if (!--l_buf_left) {\ l_buf_pnt = write_buffer();\ l_buf_left = SAVE_OBJECT_BUFSIZE;\ }\ } #define L_PUTC_EPILOG buf_pnt = l_buf_pnt; buf_left = l_buf_left; static char save_file_suffix[] = ".o"; static char *save_object_bufstart, *buf_pnt; static int buf_left; static int failed; static int save_object_descriptor; static int current_id_number; /* This is used to give id numbers to * * shared values in save_object() */ static int restored_host = -1; static char *write_buffer() { char *start; start = save_object_bufstart; if ( write( save_object_descriptor, start, SAVE_OBJECT_BUFSIZE ) != SAVE_OBJECT_BUFSIZE ) failed = 1; return start; } /* save_object has to take care of reused or recursive arrays/mappings. * * To handle small and large amounts of pointers equally well, which are * unpredictable due to the recursive definition of arrays/mappings, * a two dimensional hash structure is used, with uninitialized hash tables. * A bit vector indicates which entries are used, and which are used for * hash tables instead of single entries. */ struct pointer_record { long key; struct pointer_record *next, *next_all; long id_number; long ref_count; }; static struct pointer_record *all_pointer_records, **pointer_table; static char hash_usage[64]; struct sub_table { struct pointer_record *records[256]; char used[32]; struct sub_table *next_all; }; static struct sub_table *all_sub_tables; static char number_buffer[36]; void init_pointer_table(space) struct pointer_record **space; { pointer_table = space; bzero(hash_usage, sizeof hash_usage); all_pointer_records = 0; all_sub_tables = 0; } void free_pointer_table() { struct pointer_record *record; struct sub_table *table; for (record = all_pointer_records; record;) { struct pointer_record *next = record->next_all; xfree((char *)record); record = next; } for (table = all_sub_tables; table;) { struct sub_table *next = table->next_all; xfree((char *)table); table = next; } } static int recall_pointer(pointer) char *pointer; { extern struct vector null_vector; long key; int hash; struct pointer_record *record; key = (long)pointer; /* we know for sure that we will find the key, because it has been * registered before. */ hash = key ^ key >> 16; hash ^= hash >> 8; hash &= 0xff; record = pointer_table[hash]; if ( hash_usage[1 + (hash >> 2 & ~1)] & 1 << (hash & 7) ) { hash = (key ^ key >> 16) & 0xff; record = ((struct sub_table *)record)->records[hash]; while (record->key != key) record = record->next; } if (!record->ref_count) /* Used only once. No need for special treatment. */ return 0; if (pointer == (char*)&null_vector) /* Sharing enforced by the game driver */ return 0; { int old_id, id; char *source, c; L_PUTC_PROLOG if ( !(old_id = id = record->id_number) ) { id = ++current_id_number; record->id_number = id; } L_PUTC('<') source = number_buffer; (void)sprintf(source, "%d", id); c = *source++; do L_PUTC(c) while (c = *source++); L_PUTC('>') if (old_id) { /* has been written before */ L_PUTC_EPILOG return 1; } L_PUTC('=') L_PUTC_EPILOG return 0; } } /* * Similar to fwrite, but escapes all funny characters; * Used by save_object(). */ static void save_string(src) register char *src; { register char c; L_PUTC_PROLOG L_PUTC('\"') while (c = *src++) { if (isescaped(c)) { switch(c) { case '\007': c = 'a'; break; case '\b' : c = 'b'; break; case '\t' : c = 't'; break; case '\n' : c = 'n'; break; case '\013': c = 'v'; break; case '\014': c = 'f'; break; case '\r' : c = 'r'; break; } L_PUTC('\\') } L_PUTC(c) } L_PUTC('\"') L_PUTC_EPILOG } static void save_svalue PROT((struct svalue *, int)); static void save_array PROT((struct vector *)); static int restore_size PROT((char **str)); INLINE static int restore_array PROT((struct svalue *, char **str)); static int restore_svalue PROT((struct svalue *, char **, int)); int register_pointer PROT((char *)); static void register_array PROT((struct vector *)); #ifdef MAPPINGS extern void free_mapping PROT((struct mapping*)); void check_map_for_destr PROT((struct mapping *)); extern void walk_mapping PROT(( struct mapping *, void (*)(struct svalue *, struct svalue *, char *), char * )); static void save_mapping_filter(key, data, extra) struct svalue *key, *data; char *extra; { int i; i = (p_int)extra; save_svalue(key, i ? ':' : ',' ); while (--i >= 0) save_svalue(data++, i ? ';' : ',' ); } /* * Encode a mapping into a contiguous string. */ static void save_mapping(m) struct mapping *m; { if ( recall_pointer( (char *)m ) ) return; MY_PUTC('(') MY_PUTC('[') check_map_for_destr(m); walk_mapping(m, save_mapping_filter, (char *)(p_int)m->num_values); MY_PUTC(']') MY_PUTC(')') } struct rms_parameters { char *str; int num_values; }; static int restore_map_size(parameters) struct rms_parameters *parameters; { char *pt; int siz, tsiz; int num_values = -1, current_num_values = 0; pt = parameters->str; siz = 0; if (!pt) return -1; while (1) { switch (*pt) { case ']': { if (pt[1] != ')') return -1; parameters->str = &pt[2]; parameters->num_values = siz ? num_values : 1; return siz; } case '\"': { int backslashes; do { pt = strchr(&pt[1],'\"'); if (!pt) return -1; /* the quote is escaped if and only * if the number of slashes is odd. */ for (backslashes = -1; pt[backslashes] == '\\'; backslashes--) ; } while ( !(backslashes & 1) ) ; pt++; break; } case '(': { parameters->str = pt + 2; if (pt[1] == '{') tsiz = restore_size(¶meters->str); else if (pt[1] == '[') tsiz = restore_map_size(parameters); else return -1; pt = parameters->str; if (tsiz < 0) return -1; break; } case '<': { pt = strchr(pt, '>'); if (!pt) return -1; pt++; if (pt[0] == '=') { pt++; continue; } break; } case '-': pt++; if (!*pt) return -1; /* fall through */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (pt[1] == '.') { char *pt2; pt2 = strpbrk(pt, "=:;,"); if (!pt2) return -1; if (*pt2 != '=') break; pt = strchr(pt2, ':'); if (!pt) return -1; pt++; } /* fall through */ default: { pt = strpbrk(pt, ":;,"); if (!pt) return -1; break; } } switch (*pt) { case ':': if (current_num_values) return -1; case ';': current_num_values++; break; case ',': siz++; if (current_num_values != num_values) { if (num_values >= 0) return -1; num_values = current_num_values; } current_num_values = 0; break; default: return -1; } pt++; } return -1; } INLINE static int restore_mapping(svp, str) struct svalue *svp; char **str; { extern struct mapping *allocate_mapping PROT((int, int)); struct svalue *get_map_lvalue PROT((struct mapping*, struct svalue*, int)); struct mapping *z; struct svalue key, *data; int i; char *pt; struct rms_parameters tmp_par; int siz; tmp_par.str = *str; siz = restore_map_size(&tmp_par); if (siz < 0) { *svp = const0; return 0; } z = allocate_mapping(siz, tmp_par.num_values); svp->type = T_MAPPING; svp->u.map = z; while (--siz >= 0) { i = tmp_par.num_values; key.type = T_NUMBER; if (!restore_svalue(&key, str, i ? ':' : ',' )) { free_svalue(&key); return 0; } data = get_map_lvalue(z, &key, 1); free_svalue(&key); while (--i >= 0) { if (!restore_svalue(data++, str, i ? ';' : ',' )) return 0; } } pt = *str; if (*pt++ != ']' || *pt++ != ')' ) { free_mapping(z); *svp = const0; return 0; } *str = pt; return 1; } static void register_mapping PROT((struct mapping *map)); static void register_mapping_filter(key, data, extra) struct svalue *key, *data; char *extra; { int i; if (key->type == T_POINTER) { register_array (key->u.vec); } else if (key->type == T_MAPPING) { register_mapping(key->u.map); } for (i = (p_int)extra; --i >= 0; data++) { if (data->type == T_POINTER) { register_array (data->u.vec); } else if (data->type == T_MAPPING) { register_mapping(data->u.map); } } } static void register_mapping(map) struct mapping *map; { if ( register_pointer( (char *)(map) ) ) return; walk_mapping(map, register_mapping_filter, (char *)(p_int)map->num_values); } #endif /* MAPPINGS */ /* * Encode an element into a contiguous string. */ static void save_svalue(v, delimiter) struct svalue *v; int delimiter; { switch(v->type) { case T_STRING: save_string(v->u.string); break; case T_POINTER: save_array(v->u.vec); break; case T_NUMBER: { L_PUTC_PROLOG char *source, c; source = number_buffer; (void)sprintf(source, "%d", v->u.number); c = *source++; do L_PUTC(c) while (c = *source++); L_PUTC(delimiter); L_PUTC_EPILOG return; } case T_FLOAT: { L_PUTC_PROLOG char *source, c; source = number_buffer; (void)sprintf(source, "%.12e=%x:%x", READ_DOUBLE(v), v->x.exponent & 0xffff, v->u.mantissa); c = *source++; do L_PUTC(c) while (c = *source++); L_PUTC(delimiter); L_PUTC_EPILOG return; } #ifdef MAPPINGS case T_MAPPING: save_mapping(v->u.map); break; #endif MAPPINGS default: { L_PUTC_PROLOG L_PUTC('0'); /* Objects can't be saved. */ L_PUTC(delimiter); L_PUTC_EPILOG return; } } MY_PUTC(delimiter); } /* * Encode an array of elements into a contiguous string. */ static void save_array(v) struct vector *v; { int i; struct svalue *val; if ( recall_pointer( (char *)v ) ) return; { L_PUTC_PROLOG L_PUTC('(') L_PUTC('{') L_PUTC_EPILOG } for (i = v->size, val = v->item; --i >= 0; ) { save_svalue(val++, ','); } { L_PUTC_PROLOG L_PUTC('}') L_PUTC(')') L_PUTC_EPILOG } } static void register_array(vec) struct vector *vec; { struct svalue *v; int i; if ( register_pointer( (char *)(vec) ) ) return; v = vec->item; for (i = vec->size; --i >= 0; v++) { if (v->type == T_POINTER) { register_array (v->u.vec); } else if (v->type == T_MAPPING) { register_mapping(v->u.map); } } } long current_pointer_id; static struct svalue *shared_restored_values; static long max_shared_restored, current_shared_restored; int register_pointer(pointer) char *pointer; { long key; int hash, mask; char *usage_p; struct pointer_record *old, *new, **insert; key = (long)pointer; hash = key ^ key >> 16; hash ^= hash >> 8; hash &= 0xff; mask = 1 << (hash & 7); usage_p = hash_usage + (hash >> 2 & ~1); insert = &pointer_table[hash]; old = 0; if (usage_p[0] & mask) switch (0) { default: /* The place in the main hash table has been used before */ if (usage_p[1] & mask) { /* this place is already used for a sub - hash-table. */ struct sub_table *table; table = *(struct sub_table**)insert; hash = (key ^ key >> 16) & 0xff; mask = 1 << (hash & 7); usage_p = &table->used[hash >> 3]; insert = &table->records[hash]; if ( !(usage_p[0] & mask) ) /* insert in free place */ break; old = *insert; do { if (old->key == key) { old->ref_count++; return 1; } } while (old = old->next); old = *insert; /* insert at top of sub hash chain */ break; } else { struct sub_table *table; int old_hash; old = *insert; if (old->key == key) { old->ref_count++; return 1; } /* create a new sub - hash-table. */ usage_p[1] |= mask; table = (struct sub_table *)xalloc(sizeof *table); *insert = (struct pointer_record *)table; table->next_all = all_sub_tables; all_sub_tables = table; bzero(table->used, sizeof table->used); old_hash = (old->key ^ old->key >> 16) & 0xff; table->used[old_hash >> 3] |= 1 << (old_hash & 7); table->records[old_hash] = old; hash = (key ^ key >> 16) & 0xff; if (hash != old_hash) { old = 0; } insert = &table->records[hash]; mask = 1 << (hash & 7); usage_p = &table->used[hash >> 3]; } } usage_p[0] |= mask; new = (struct pointer_record *)xalloc(sizeof *new); *insert = new; new->key = key; new->next = old; new->next_all = all_pointer_records; new->ref_count = 0; new->id_number = 0; all_pointer_records = new; return 0; } /* * Save an object to a file. * The routine checks with the function "valid_write()" in /obj/master.c * to assertain that the write is legal. */ void save_object(ob, file) struct object *ob; char *file; { static char save_object_header[] = { '#', SAVE_OBJECT_VERSION, ':', SAVE_OBJECT_HOST, '\n' }; char *name, *tmp_name, save_buffer[SAVE_OBJECT_BUFSIZE]; struct pointer_record *pointer_table_space[256]; int len, i; int f; struct svalue *v; struct variable *names; if (ob->flags & O_DESTRUCTED) return; /* COMPAT_MODE stuff moved to master.c by amylaar * master.c will access current_object->prog->name instead of * current_prog->name , but then, this is probably better in COMPAT_MODE * anyway. */ file = check_valid_path(file, ob, "save_object", 1); if (file == 0) error("Illegal use of save_object()\n"); len = strlen(file); name = alloca(len + (sizeof save_file_suffix) + len + (sizeof save_file_suffix) + 4); tmp_name = name + len + sizeof save_file_suffix; (void)strcpy(name, file); #ifndef MSDOS_FS (void)strcpy(name+len, save_file_suffix); #endif /* * Write the save-files to different directories, just in case * they are on different file systems. */ sprintf(tmp_name, "%s.tmp", name); #ifdef MSDOS_FS (void)strcpy(name+len, save_file_suffix); #endif save_object_descriptor = f = ixopen3(tmp_name, O_CREAT|O_TRUNC|O_WRONLY, 0640); if (f < 0) { error("Could not open %s for a save.\n", tmp_name); } /* identify arrays/mappings that are used more than once.*/ init_pointer_table(pointer_table_space); v = ob->variables; names = ob->prog->variable_names; for (i = ob->prog->num_variables; --i >= 0; v++, names++) { if (names->flags & TYPE_MOD_STATIC) continue; if (v->type == T_POINTER) { register_array (v->u.vec); } else if (v->type == T_MAPPING) { register_mapping(v->u.map); } } failed = 0; current_id_number = 0; save_object_bufstart = save_buffer; memcpy(save_buffer, save_object_header, sizeof(save_object_header)); buf_left = SAVE_OBJECT_BUFSIZE - sizeof(save_object_header); buf_pnt = save_buffer + sizeof(save_object_header); v = ob->variables; names = ob->prog->variable_names; for (i = ob->prog->num_variables; --i >= 0; v++, names++) { if (names->flags & TYPE_MOD_STATIC) continue; { char *var_name, c; L_PUTC_PROLOG var_name = names->name; c = *var_name++; do { L_PUTC(c) } while (c = *var_name++); L_PUTC(' ') L_PUTC_EPILOG } save_svalue(v, '\n'); } free_pointer_table(); if ( write( save_object_descriptor, save_object_bufstart, SAVE_OBJECT_BUFSIZE-buf_left ) != SAVE_OBJECT_BUFSIZE-buf_left ) failed = 1; if (failed) { (void)close(f); unlink(tmp_name); add_message("Failed to save to file. Disk could be full.\n"); return; } (void)unlink(name); #if !defined(MSDOS_FS) && !defined(AMIGA) if (link(tmp_name, name) == -1) #else /* MSDOS_FS */ (void) close(f); if (rename(tmp_name,name) < 0) #endif { perror(name); printf("Failed to link %s to %s\n", tmp_name, name); add_message("Failed to save object !\n"); } #if !defined(MSDOS_FS) && !defined(AMIGA) (void)close(f); unlink(tmp_name); #endif } /* * Find the size of an array. Return -1 for failure. */ static int restore_size(str) char **str; { char *pt,*pt2; int siz,tsiz; pt = *str; siz = 0; while ((pt) && (*pt)) { if (pt[0] == '}') { if (pt[1] != ')') return -1; *str = &pt[2]; return siz; } if (pt[0] == '\"') { int backslashes; do { pt = strchr(&pt[1],'\"'); if (!pt) return -1; /* the quote is escaped if and only if the number of slashes is odd. */ for (backslashes = -1; pt[backslashes] == '\\'; backslashes--) ; } while ( !(backslashes & 1) ) ; if (pt[1] != ',') return -1; siz++; pt += 2; } else if (pt[0] == '(') { /* Lazy way of doing it, a bit inefficient */ struct rms_parameters tmp_par; tmp_par.str = pt + 2; if (pt[1] == '{') tsiz = restore_size(&tmp_par.str); #ifdef MAPPINGS else if (pt[1] == '[') tsiz = restore_map_size(&tmp_par); #endif else return -1; pt = tmp_par.str; if (tsiz < 0) return -1; pt++; siz++; } else if (pt[0] == '<') { pt = strchr(pt, '>'); if (!pt) return -1; if (pt[1] == ',') { siz++; pt += 2; } else if (pt[1] == '=') { pt += 2; } else return -1; } else { pt2 = strchr(pt, ','); if (!pt2) return -1; siz++; pt = &pt2[1]; } } return -1; } INLINE static int restore_array(svp, str) struct svalue *svp; char **str; { struct vector *v; char *pt, *end; int siz; end = *str; siz = restore_size(&end); if (siz < 0) { *svp = const0; return 0; } v = allocate_array(siz); /* We need to do this now, so that the value can be used inside. */ svp->type = T_POINTER; svp->u.vec = v; for ( svp = v->item; --siz >= 0; svp++) { if ( !restore_svalue(svp, str, ',') ) { return 0; } } pt = *str; if (*pt++ != '}' || *pt++ != ')' ) { return 0; } *str = pt; return 1; } static int restore_svalue(svp, pt, delimiter) struct svalue *svp; char **pt; int delimiter; { char *cp; switch( *(cp = *pt) ) { case '\"': { char *source, *start, c; start = cp; source = cp+1; for(;;) { if ( !(c = *source++) ) { *svp = const0; return 0; } #if 1 /* for compatiblity reasons */ #ifndef MSDOS_FS if (c == '\r') #else if (c == 30) #endif c = '\n'; #endif if (c == '\\') { if ( !(c = *source++) ) { *svp = const0; return 0; /* String ends with a \\ buggy probably */ } switch(c) { case 'a': c = '\007'; break; case 'b': c = '\b' ; break; case 't': c = '\t' ; break; case 'n': c = '\n' ; break; case 'v': c = '\013'; break; case 'f': c = '\014'; break; case 'r': c = '\r' ; break; } } else if (c == '\"') break; *cp++ = c; } *cp=0; *pt = source; svp->type = T_STRING; svp->x.string_type = STRING_SHARED; svp->u.string = make_shared_string(start); break; } case '(': #ifdef MAPPINGS *pt = cp+2; switch ( cp[1] ) { case '[': { if ( !restore_mapping(svp, pt) ) { return 0; } break; } #endif /* MAPPINGS */ case '{': { if ( !restore_array(svp, pt) ) { return 0; } break; } default: return 0; } break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { char c, *numstart = cp; int nega = 0; long l = 0; if (*cp == '-') { nega = 1; cp++; } while(lexdigit(c = *cp++)) l = (((l << 2) + l) << 1) + (c - '0'); if (c != '.') { svp->type = T_NUMBER; svp->u.number = nega ? -l : l; *pt = cp; return c == delimiter; } svp->type = T_FLOAT; if ( (cp = strchr(cp, '=')) && restored_host == CURRENT_HOST) { int tmp; cp++; if (sscanf(cp, "%x:%x", &tmp, &svp->u.mantissa) != 2) return 0; svp->x.exponent = tmp; } else { STORE_DOUBLE_USED double d; d = atof(cp = numstart); STORE_DOUBLE(svp, d); } cp = strchr(cp, delimiter); *pt = cp+1; return (int)cp; } case '<': { int id; id = atoi(cp+1); cp = strchr(cp, '>'); if (!cp) { *svp = const0; return 0; } if (cp[1] == '=') { int res; *pt = cp+2; /* Shared values can be used even before they have been read in * completely. */ if (id != ++current_shared_restored) { *svp = const0; return 0; } if (id > max_shared_restored) { max_shared_restored <<= 1; shared_restored_values = (struct svalue *) realloc((char*)shared_restored_values, max_shared_restored); } id--; res = restore_svalue(&shared_restored_values[id], pt, delimiter); *svp = shared_restored_values[id]; return res; } if (id <= 0 || id > current_shared_restored) { *svp = const0; return 0; } assign_svalue_no_free(svp, &shared_restored_values[id-1]); cp = strchr(cp, delimiter); *pt = cp+1; return (int)cp; } default: *svp = const0; return 0; } cp = *pt; if (*cp++ != delimiter) return 0; *pt = cp; return 1; } /* Needed to restore backslashes correctly... */ static int old_restore_string(v, str) struct svalue *v; char *str; { char *cp, c; cp = ++str; if (c = *cp++) { do { #ifndef MSDOS_FS if (c == '\r') #else if (c == 30) #endif cp[-1] = '\n'; } while (c = *cp++); if (cp[-2] == '\n' && cp[-3] == '\"') { cp[-3] = '\0'; v->type = T_STRING; v->x.string_type = STRING_SHARED; v->u.string = make_shared_string(str); return 1; } } *v = const0; return 0; } int restore_object(ob, file) struct object *ob; char *file; { char *name, *var, *buff, *space; int len; FILE *f; struct stat st; struct variable *rover; /* rover is used uninitialised, but var_rest is initialised instead. */ int var_rest, num_var; int old_format; struct discarded { struct svalue v; struct discarded *next; } * dp = 0; if (current_object != ob) fatal("Bad argument to restore_object()\n"); if (ob->flags & O_DESTRUCTED) return 0; file = check_valid_path(file, ob, "restore_object", 0); if (file == 0) error("Illegal use of restore_object()\n"); len = strlen(file); name = alloca(len + (sizeof save_file_suffix)); (void)strcpy(name, file); if (name[len-2] == '.' && name[len-1] == 'c') len -= 2; (void)strcpy(name+len, save_file_suffix); f = fopen(name, "r"); if (!f || fstat(fileno(f), &st) == -1) { if (f) (void)fclose(f); return 0; } if (st.st_size == 0) { (void)fclose(f); return 0; } buff = alloca(st.st_size + 1); shared_restored_values = (struct svalue *) xalloc(sizeof(struct svalue)*256); max_shared_restored = 256; current_shared_restored = 0; num_var = ob->prog->num_variables; var_rest = 0; old_format = 1; restored_host = -1; while(1) { struct svalue *v; char *pt; if (fgets(buff, st.st_size + 1, f) == 0) break; /* Remember that we have a newline at end of buff ! */ space = strchr(buff, ' '); if (space == 0) { if (buff[0] == '#') { int i; int restored_version; i = sscanf(buff+1, "%d:%d", &restored_version, &restored_host); if (i <= 0 || i == 2 && restored_version <= CURRENT_VERSION) { old_format = 0; continue; } } (void)fclose(f); if (dp) do free_svalue(&dp->v); while (dp=dp->next); xfree((char*)shared_restored_values); error("Illegal format when restoring %s.\n", name); return 0; /* flow control hint */ } *space = 0; do { if ( var = findstring(buff) ) { do rover++; while (--var_rest > 0 && (rover->name != var || rover->flags & TYPE_MOD_STATIC) ); if (var_rest <= 0) { rover = ob->prog->variable_names-1; var_rest = num_var + 1; do rover++; while (--var_rest > 0 && (rover->name != var || rover->flags & TYPE_MOD_STATIC) ); if (var_rest > 0) continue; } else continue; } { struct discarded *tmp; tmp = dp; dp = (struct discarded *)alloca(sizeof(struct discarded)); dp->next = tmp; v = &dp->v; v->type = T_NUMBER; break; } } while ((v = &ob->variables[num_var-var_rest]),MY_FALSE); free_svalue(v); pt = space+1; if ( old_format && pt[0] == '\"' ? !old_restore_string(v, pt) : !restore_svalue(v, &pt, '\n') ) { (void)fclose(f); if (dp) do free_svalue(&dp->v); while (dp=dp->next); xfree((char*)shared_restored_values); error("Illegal format when restoring %s.\n", name); return 0; } } if (dp) do free_svalue(&dp->v); while (dp=dp->next); if (d_flag > 1) debug_message("Object %s restored from %s.\n", ob->name, name); (void)fclose(f); xfree((char*)shared_restored_values); return 1; } void tell_npc(ob, str) struct object *ob; char *str; { push_volatile_string(str); (void)sapply("catch_tell", ob, 1); } /* * Send a message to an object. * If it is an interactive object, it will go to his * screen. Otherwise, it will go to a local function * catch_tell() in that object. This enables communications * between players and NPC's, and between other NPC's. */ void tell_object(ob, str) struct object *ob; char *str; { struct object *save_command_giver; if (ob->flags & O_DESTRUCTED) return; if (ob->interactive) { save_command_giver = command_giver; command_giver = ob; add_message("%s", str); command_giver = save_command_giver; return; } tell_npc(ob, str); } #ifdef DEBUG void _free_object(ob, from) struct object *ob; char *from; { ob->ref--; if (d_flag > 1) printf("Subtr ref to ob %s: %d (%s)\n", ob->name, ob->ref, from); if (ob->ref > 0) return; if (d_flag) printf("free_object: %s.\n", ob->name); if (!(ob->flags & O_DESTRUCTED)) { /* This is fatal, and should never happen. */ fatal("FATAL: Object 0x%x %s ref count 0, but not destructed (from %s).\n", ob, ob->name, from); } #else /* DEBUG */ int _free_object(ob) struct object *ob; { #endif /* DEBUG */ if (ob->interactive) fatal("Tried to free an interactive object.\n"); /* * If the program is freed, then we can also free the variable * declarations. */ if (ob->swap_num != -1) remove_swap_file(ob); if (ob->prog) { tot_alloc_object_size -= ob->prog->num_variables * sizeof (struct svalue) + sizeof (struct object) - sizeof (struct svalue); free_prog(ob->prog, 1); ob->prog = 0; } if (ob->name) { if (d_flag > 1) debug_message("Free object %s\n", ob->name); if (lookup_object_hash(ob->name) == ob) fatal("Freeing object %s but name still in name table", ob->name); xfree(ob->name); ob->name = 0; } tot_alloc_object--; xfree((char *)ob); #ifndef DEBUG return 0; #endif } #ifndef add_ref #ifndef DEBUG INLINE #endif void add_ref(ob, from) struct object *ob; char *from; { ob->ref++; #ifdef DEBUG if (d_flag > 1) printf("Add reference to object %s: %d (%s)\n", ob->name, ob->ref, from); #endif } #endif /* * Allocate an empty object, and set all variables to 0. Note that a * 'struct object' already has space for one variable. So, if no variables * are needed, we allocate a space that is smaller than 'struct object'. This * unused (last) part must of course (and will not) be referenced. */ #ifdef INITALIZATION_BY___INIT struct object *get_empty_object(num_var, variables) int num_var; struct variable *variables; #else struct object *get_empty_object(num_var, variables, initializers) int num_var; struct variable *variables; struct svalue *initializers; #endif { static struct object NULL_object = {0}; struct object *ob; int size = sizeof (struct object) - sizeof(struct svalue) + num_var * sizeof (struct svalue); int i; tot_alloc_object++; tot_alloc_object_size += size; ob = (struct object *)xalloc(size); /* marion * Don't initialize via memset, this is incorrect. E.g. the bull machines * have a (char *)0 which is not zero. We have structure assignment, so * use it. */ /* amylaar : * structure assignment might be nice, but I prefer consistent statistics. * Therefore, memcpy() is more appropriate. Besides, portability also * requires initializing the NULL_object... */ memcpy((char *)ob, (char *)&NULL_object, sizeof(struct object) - sizeof(struct svalue)); ob->ref = 1; ob->swap_num = -1; #ifdef DEBUG ob->extra_num_variables = num_var; #endif for (i = num_var; --i >= 0; ) { #ifndef INITALIZATION_BY___INIT if (variables[i].flags & NAME_INITIALIZED) { assign_svalue_no_free(&ob->variables[i], &initializers[i]); } else #endif ob->variables[i] = const0; } return ob; } #if 0 void dump_all_objects() { struct object *ob; FILE *d; d = fopen("OBJ_DUMP", "w"); if (!d) { add_message("Couldn't open dump file.\n"); return; } add_message("Dumping data to 'OBJ_DUMP'... "); for (ob = obj_list; ob; ob = ob->next_all) { fprintf(d, "%s (0x%x) ", ob->name, (unsigned long)ob); if (ob->super) fprintf(d, "Super %s (0x%x)", ob->super->name, (unsigned long)ob->super); fprintf(d, "\t Ref %2d Enbl_cmd %d Clnd %d Hrt_bt %d lang ref %d swp %d hp %x\n", ob->ref, ob->flags & O_ENABLE_COMMANDS, ob->flags & O_CLONE, ob->flags & O_HEART_BEAT, ob->prog == 0 ? 1 : ob->prog->ref, ob->flags & O_SWAPPED, ob->prog->heart_beat); } fclose(d); add_message("DONE\n"); } #endif void remove_all_objects() { struct object *ob; struct svalue v; v.type = T_OBJECT; while(1) { if (obj_list == 0) break; ob = obj_list; v.u.ob = ob; destruct_object(&v); if (ob == obj_list) break; } remove_destructed_objects(); } #if 0 /* * For debugging purposes. */ void check_ob_ref(ob, from) struct object *ob; char *from; { struct object *o; int i; for (o = obj_list, i=0; o; o = o->next_all) { if (o->inherit == ob) i++; } if (i+1 > ob->ref) { fatal("FATAL too many references to inherited object %s (%d) from %s.\n", ob->name, ob->ref, from); if (current_object) fprintf(stderr, "current_object: %s\n", current_object->name); for (o = obj_list; o; o = o->next_all) { if (o->inherit != ob) continue; fprintf(stderr, " %s\n", ob->name); } } } #endif /* 0 */ static struct object *hashed_living[LIVING_HASH_SIZE]; static int num_living_names, num_searches = 1, search_length = 1; static int hash_living_name(str) char *str; { #if 1 return hashstr(str, 100, LIVING_HASH_SIZE); #else unsigned ret = 0; while(*str) ret = ret * 2 + *str++; return ret % LIVING_HASH_SIZE; #endif } struct object *find_living_object(str, player) char *str; int player; { struct object **obp, *tmp; struct object **hl; int mask; num_searches++; hl = &hashed_living[hash_living_name(str)]; mask = player ? O_ONCE_INTERACTIVE : O_ENABLE_COMMANDS; for (obp = hl; *obp; obp = &(*obp)->next_hashed_living) { search_length++; if ( !((*obp)->flags & mask) ) continue; if (strcmp((*obp)->living_name, str) == 0) break; } if (*obp == 0) return 0; /* Move the found ob first. */ if (obp == hl) return *obp; tmp = *obp; *obp = tmp->next_hashed_living; tmp->next_hashed_living = *hl; *hl = tmp; return tmp; } void set_living_name(ob, str) struct object *ob; char *str; { struct object **hl; if (ob->flags & O_DESTRUCTED) return; if (ob->living_name) { remove_living_name(ob); } num_living_names++; hl = &hashed_living[hash_living_name(str)]; ob->next_hashed_living = *hl; *hl = ob; ob->living_name = make_shared_string(str); #if 0 /* This is not a pretty way to find out if it is a wizard */ if (ob->interactive) { struct svalue *v; v = apply("query_level", ob, 0); if (v && v->type == T_NUMBER && v->u.number >= 21) ob->flags |= O_IS_WIZARD; } #endif return; } void remove_living_name(ob) struct object *ob; { struct object **hl; #ifdef MUDWHO sendmudwhologout(ob); #endif num_living_names--; if (!ob->living_name) fatal("remove_living_name: no living name set.\n"); hl = &hashed_living[hash_living_name(ob->living_name)]; while(*hl) { if (*hl == ob) break; hl = &(*hl)->next_hashed_living; } if (*hl == 0) fatal("remove_living_name: Object named %s no in hash list.\n", ob->living_name); *hl = ob->next_hashed_living; free_string(ob->living_name); ob->next_hashed_living = 0; ob->living_name = 0; } void stat_living_objects() { char print_buff[12]; sprintf(print_buff, "%5.2f", (float)search_length / (float)num_searches); add_message("\ Hash table of living objects:\n\ -----------------------------\n\ %d living named objects, average search length:%s\n", num_living_names, print_buff); } void reference_prog (progp, from) struct program *progp; char *from; { progp->ref++; if (d_flag) printf("reference_prog: %s ref %d (%s)\n", progp->name, progp->ref, from); } void do_free_sub_strings(num_strings, strings, num_variables, variable_names) int num_strings, num_variables; char **strings; struct variable *variable_names; { int i; /* Free all strings */ for (i=0; i < num_strings; i++) free_string(strings[i]); /* Free all variable names */ for (i = num_variables; --i >= 0; ) { free_string(variable_names[i].name); } } /* * Decrement reference count for a program. If it is 0, then free the program. * The flag free_sub_strings tells if the propgram plus all used strings * should be freed. They normally are, except when objects are swapped, * as we want to be able to read the program in again from the swap area. * That means that strings are not swapped. */ void free_prog(progp, free_sub_strings) struct program *progp; int free_sub_strings; { progp->ref--; if (progp->ref > 0) return; if (d_flag) printf("free_prog: %s\n", progp->name); if (progp->ref < 0) fatal("Negative ref count for prog ref.\n"); total_prog_block_size -= progp->total_size; total_num_prog_blocks -= 1; if (free_sub_strings) { int i; unsigned char *program = progp->program; uint32 *functions = progp->functions; /* Free all function names. */ for (i = progp->num_functions; --i >= 0; ) { if ( !(functions[i] & NAME_INHERITED) ) { char *name; memcpy( (char *)&name, program + (functions[i] & FUNSTART_MASK) - 1 - sizeof name, sizeof name ); free_string(name); } } do_free_sub_strings( progp->num_strings, progp->strings, progp->num_variables, progp->variable_names); /* Free all inherited objects */ for (i=0; i < progp->num_inherited; i++) free_prog(progp->inherit[i].prog, 1); xfree(progp->name); } xfree((char *)progp); } void reset_object(ob, arg) struct object *ob; int arg; { extern int current_time; /* Be sure to update time first ! */ ob->next_reset = current_time + TIME_TO_RESET/2 + random_number(TIME_TO_RESET/2); #ifdef INITIALIZATION_BY___INIT if (arg == 0) sapply("__INIT", ob, 0); #endif #ifndef NATIVE_MODE push_number(arg); (void)sapply("reset", ob, 1); #endif #ifndef COMPAT_MODE if (arg == 0) { sapply("create", ob, 0); } #ifdef NATIVE_MODE else { sapply("reset", ob, 0); } #endif /* NATIVE_MODE */ #endif /* COMPAT_MODE */ ob->flags |= O_RESET_STATE; } struct replace_ob *obj_list_replace = (struct replace_ob *)0; void replace_programs() { extern void replace_program_lambda_adjust PROT((struct replace_ob *r_ob, struct program *)); struct replace_ob *r_ob, *r_next; int i,j; struct svalue *svp; #ifdef DEBUG if (d_flag) debug_message("start of replace_programs\n"); #endif for (r_ob=obj_list_replace; r_ob; r_ob = r_next) { struct program *old_prog; if (r_ob->ob->flags & O_SWAPPED) load_ob_from_swap(r_ob->ob); i = r_ob->ob->prog->num_variables - r_ob->new_prog->num_variables; #ifdef DEBUG if (d_flag) debug_message("%d less variables\n",i); #endif tot_alloc_object_size -= i * sizeof(struct svalue[1]); svp = r_ob->ob->variables; j = r_ob->var_offset; i -= j; #ifdef DEBUG if (d_flag) debug_message("freeing %d variables:\n",j); #endif while (--j >= 0) free_svalue(svp++); #ifdef DEBUG if (d_flag) debug_message("freed.\n"); #endif j = r_ob->new_prog->num_variables; memcpy( (char *)r_ob->ob->variables, (char *)svp, j * sizeof(struct svalue[1]) ); svp += j; #ifdef DEBUG if (d_flag) debug_message("freeing %d variables:\n",i); #endif while (--i >= 0) free_svalue(svp++); #ifdef DEBUG if (d_flag) debug_message("freed.\n"); #endif r_ob->new_prog->ref++; old_prog = r_ob->ob->prog; r_ob->ob->prog = r_ob->new_prog; r_ob->ob->flags |= O_CLONE; r_next = r_ob->next; if (r_ob->lambda_rpp) { obj_list_replace = r_next; replace_program_lambda_adjust(r_ob, old_prog); } free_prog(old_prog, 1); #ifdef DEBUG if (d_flag) debug_message("program freed.\n"); #endif if (r_ob->ob->shadowing) { /* The master couldn't decide if it's a legal shadowing before * the program was actually replaced. It is possible that the * blueprint to the replacing program is already destructed, * and it's source changed. * On the other hand, if we called the master now, all kind of * volatile data structures could result, even new entries for * obj_list_replace. This would eventually require to reference it, * and all the lrpp's , in check_a_lot_ref_counts() and * garbage_collection() . Being able to use replace_program() in * shadows is hardly worth this effort. Thus, we simply stop * the shadowing. */ r_ob->ob->shadowing->shadowed = r_ob->ob->shadowed; if (r_ob->ob->shadowed) { r_ob->ob->shadowed->shadowing = r_ob->ob->shadowing; r_ob->ob->shadowed = 0; } r_ob->ob->shadowing = 0; } xfree((char *)r_ob); } obj_list_replace = (struct replace_ob *)0; #ifdef DEBUG if (d_flag) debug_message("end of replace_programs\n"); #endif } /* * If there is a shadow for this object, then the message should be * sent to it. But only if catch_tell() is defined. Beware that one of the * shadows may be the originator of the message, which means that we must * not send the message to that shadow, or any shadows in the linked list * before that shadow. */ int shadow_catch_message(ob, str) struct object *ob; char *str; { if (!ob->interactive->catch_tell_activ || ob == current_object) return 0; trace_level |= ob->interactive->trace_level; push_volatile_string(str); if (sapply("catch_tell", ob, 1)) return 1; /* The call failed, thus, current_object wasn't changed * (e.g. destructed and set to 0 ) . * !current_object is true when a prompt is given. */ if (!current_object || !current_object->shadowing) ob->interactive->catch_tell_activ = 0; return 0; } #if 0 int shadow_catch_message(ob, str) struct object *ob; char *str; { if (!ob->shadowed) return 0; while(ob->shadowed != 0 && ob->shadowed != current_object) ob = ob->shadowed; while(ob->shadowing) { if (function_exists("catch_tell", ob)) { push_volatile_string(str); if (apply("catch_tell", ob, 1)) /* this will work, since we know the */ /* function is defined */ return 1; } ob = ob->shadowing; } return 0; } #endif /* 0 */