#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include "stack.h"
#include "mod_obj.h"
/*
* Find a variable offset using stored names (yuck!).
*/
#define MAX_SAVE_OBJECT_DEPTH 2
#if 0
char * global_by_num(Class *ob, int vnum)
{
return ob->global_table[vnum]->u.string->str;
}
#endif
int find_variable(Code * ob, char * buff, int offset)
{
int n, i;
n = ob->num_variables;
for (i = 0; i < n; i++) {
if (!strcmp(ob->global_table[i]->u.string->str, buff)) {
#if 0
printf("RO: returning offset %d\n", i+offset);
#endif
return i+offset;
}
}
return -1;
}
#if 0
/*
* Replace newlines in a string with a carriage return, to make the string
* writeable on one line.
*/
static void replace_newline(char *str)
{
for (; *str; str++) {
if (str[0] == '\n')
str[0] = '\r';
}
}
/*
* Replace carriage return in a string with newlines.
*/
static void restore_newline(char *str)
{
for (; *str; str++) {
if (str[0] == '\r')
str[0] = '\n';
}
}
#endif
/*
* Save an object to a file. Security is managed in interpret.c
*
* This code (internal_save & internal_restore) by
* Geoff Wong - geoff@serc.rmit.edu.au
* Mike McGaughey - mmcg@cs.monash.edu.au
*
* It is a major modification of the original (presumably written by Lars).
*/
static char tmp_name[200] = "";
static int in_save_object=0; /* temp hack: recursion check */
static int save_object_depth=0; /* temp hack: recursion check */
int internal_save(Obj *ob, char *file, FILE *flag)
{
char *name = 0, *bakname = 0;
int len, failed = 0;
FILE *f;
static int ii = 0;
int i = 0;
extern char * query_host_name();
if (!flag)
{
if (in_save_object)
{
/* failed - ignore it till I work out a proper fix. */
fprintf(stderr, "Recursive save object; ob %s, file %s\n",
ob->name->str, file);
return 1;
}
in_save_object = 1;
save_object_depth = 1;
sprintf(tmp_name, "tmp_file.%s.%d.%x", query_host_name(), getpid(), ii++);
len = strlen(file);
name = xalloc(len + 3);
(void) strcpy(name, file);
(void) strcat(name, ".o");
bakname = xalloc(len + 7);
(void) strcpy(bakname, name);
(void) strcat(bakname, ".bak");
f = fopen(tmp_name, "w");
if (f == NULL)
{
efun_error("Could not open %s for a save.\n", tmp_name);
}
force_no_su(tmp_name);
}
else
{
f = flag;
save_object_depth++;
}
for (i = 0; i < ob->num_variables; i++)
{
Val *v = &ob->variables[i];
if (ob->global_table[i]->type & TY_STATIC) continue;
if ((v->type & T_OBJECT) && flag) continue;
if (!failed)
if (fprintf(f, "%s ", ob->global_table[i]->u.string->str) == EOF)
failed = 1;
else
failed = emit_value(f, v);
}
save_object_depth--;
if (!flag)
{
/* NFS is asynch */
if (EOF == fclose(f)) failed = 1;
if (failed)
{
add_message("Failed to save to file. Disk could be full. Notify a God, please!\n");
fprintf(stderr, "Savefile fail (disk full?) - \"%s\"\n", name);
perror(tmp_name);
}
in_save_object = 0;
/*
* we try to provide a backup in case of problems; if writing the new
* savefile fails, rename the old savefile to ".o.bak", and don't dump a
* new one. If there is no old savefile, don't overwrite a current .o.bak,
* as it was probably caused last time we attempted a save (this'll cause
* problems if we run out of space, temporarily get more, and run out again
* - we *may* lose the full backup in some cases. But there's only so much
* we can do!)
*/
if (failed && (rename(name, bakname) == (-1)))
{
switch (errno)
{
case ENOENT: /* this is ok, the original didn't exist */
break;
default:
fprintf(stderr,
"Failed to back up the original savefile (%s -> %s);\n", name, bakname);
perror("Savefile rename");
break;
}
}
(void) unlink(name); /* discard the thing, no matter what. */
/* if we didn't fail, try to link in the new */
if ((!failed) && (link(tmp_name, name) == -1))
{
add_message("Saved temp savefile, but couldn't create \"%s\" from it - notify a god, please!\n", name);
perror(name);
fprintf(stderr, "Failed to link %s to %s\n", tmp_name, name);
}
force_no_su(name);
unlink(tmp_name);
free(name);
free(bakname);
}
return failed;
}
/* emit value - returns "failed" status. */
int emit_value(FILE *f, Val *v)
{
int failed = 0;
if (v->type & T_NUMBER)
{
if (fprintf(f, "%d\n", v->u.number) == EOF) failed = 1;
}
else if (v->type & T_REAL)
{
if (fprintf(f, "%f\n", v->u.real) == EOF) failed = 1;
}
else if (v->type & T_STRING)
{
failed = emit_new_string(f, v->u.string->str);
}
else if (v->type & T_POINTER)
{
failed = emit_vector(f, v->u.vec);
}
else if (v->type & T_OBJECT)
{
failed = emit_object(f, v->u.ob);
}
else
{
failed = (EOF == fprintf(f, "0\n"));
}
return failed;
}
/*
* NEW emit string (value, not name). Uses fully escaped char
* strings - back to the "string" notation. Puts in a CR automatically
* after a normal CR.
*/
int
emit_new_string(FILE *f, char *s)
{
char *c;
int failed = 0;
if (EOF == fprintf(f, "\""))
return 1; /* failed */
c = s;
while (*c) {
switch (*c) {
/* standard escaped things (\ then character) */
case '\\':
case '"':
failed = failed || (putc('\\', f) == EOF);
failed = failed || (putc(*c, f) == EOF);
break;
case '\n':
failed = failed || (EOF == fprintf(f, "\\n\\\n"));
break;
default:
if (!isprint(*c)) {
failed = failed || (EOF == fprintf(f, "\\%03o", *c));
}
else {
failed = failed || (EOF == putc(*c, f));
}
break;
}
c++;
}
failed = failed || (EOF == fprintf(f, "\"\n"));
return failed;
}
/*
* emit string (value, not name). Uses old new-style strings - these are
* preceded by a ' character and require no translation.
*/
int
emit_string(FILE *f, char *s)
{
char *c;
int failed = 0;
if (EOF == fprintf(f, "'"))
return 1; /* failed */
c = s;
while (*c) {
if (*c == '"' || *c == '\\' || *c == '\n')
failed = failed || (putc('\\', f) == EOF);
failed = failed || (EOF == putc(*c, f));
c++;
}
failed = failed || (EOF == fprintf(f, "\"\n"));
return failed;
}
int
emit_vector(FILE *f, struct vector *v)
{
int x = 0;
if (EOF == fprintf(f, "{ %d\n", v->size))
return 1;
while (x < v->size) {
if (emit_value(f, &v->item[x])) return 1;
x++;
}
/* should do automatic "multiple optimisation" -- geoff */
return (EOF == fprintf(f, " }\n"));
}
/* Called to emit subobjects as part of another object.
* If query_no_external_save() returns true, will not save the object.
*/
int emit_object(FILE *f, Obj *o)
{
Val * v;
if (o->destructed)
{
/* just to avoid an error log */
if (EOF == fprintf(f, "0\n")) return 1;
return 0;
}
v = apply_single(C("query_no_external_save"), o, 0);
if (v)
{
/* this is dangerous */
if (v->type == T_NUMBER && v->u.number)
{
if (EOF == fprintf(f, "0\n")) return 1;
return 0;
}
free_value(v);
}
#if 0
else {
if (EOF == fprintf(f, "0\n")) return 1;
return 0;
}
#endif
if (EOF == fprintf(f, "[ %s\n", o->name->str)) return 1;
/* if (o_read_ok(o)) */
if (o->cloned && save_object_depth < MAX_SAVE_OBJECT_DEPTH)
internal_save(o, 0, f); /* save clones */
/* we only save the names of non-clones! */
return (EOF == fprintf(f, "]\n"));
}
int internal_restore(Obj *ob, char *file, FILE *flag)
{
char * buff, *bakname;
char *name = NULL;
int len;
FILE *f;
int p;
Obj *save;
struct stat st;
int x;
save = Scurrent();
if (!flag)
{
len = strlen(file);
name = xalloc(len + 3);
(void) strcpy(name, file);
(void) strcat(name, ".o");
f = fopen(name, "r");
if (f == 0)
{
/* special case - look for a .o.bak file */
bakname = xalloc(len + 7);
strcpy(bakname, name);
strcat(bakname, ".bak");
f = fopen(bakname, "r");
if (!f) {
free(name);
free(bakname);
return 0;
}
/* ok, so we found it as an obak (but no readable .o file).
* rename it to .o, to save wizzes tearing their hair wondering
* which is the latest version. Should really check whether
* we're losing the old .o file, but anyway...
*/
if (link(bakname, name) == 0) {
unlink(bakname);
} /* else do nothing */
}
}
else f = flag;
if (fstat(fileno(f), &st) == -1) {
perror(name);
return 0;
}
if (st.st_size == 0)
return 0;
buff = xalloc(st.st_size + 1);
Sset_current(ob, "internal_restore");
while (1) {
Val *v;
x = fscanf(f, " %s ", buff);
if (x == EOF)
break;
if (x == 0) {
debug_message("ILLEGAL format when restoring %s.\n", name);
return 0;
}
if (*buff == ']' && flag) break;
p = find_variable(ob->code, buff, 0);
#if 0
printf("RO: found %s as %d.\n", buff, p);
#endif
if (p < 0 || ob->global_table[p]->type & TY_STATIC)
v = 0; /* need this to skip value in file... */
else {
v = &ob->variables[p];
clear_assign(v, Const(0)); /* ensure it is zeroed out */
}
if (read_value(f, buff, v))
break;
}
if (!flag) {
free(name);
(void) fclose(f);
}
Sset_current(save, "internal_restore 2");
free(buff);
return 1;
}
/*
* read_value reads a value, using buff as scratch pad, placing the value in
* V can be a null pointer. Assumes first character is non-space.
*/
int read_value(FILE *f, char *buff, Val *v)
{
char c;
int x, y;
if (fscanf(f, " ") == EOF)
{
debug_message("Unbalanced vector in restore\n");
return 1;
}
c = getc(f);
if (c == EOF)
return 1;
switch (c)
{
case '{': /* vector */
return read_vector(f, buff, v);
case '[':
return read_object(f, buff, v);
case '"':
return read_string(f, buff, v);
case '\'':
return read_new_string(f, buff, v);
default:
if (isdigit(c) || c == '-')
{
ungetc(c, f);
x = fscanf(f, " %d ", &y);
if (x != 1) return 1;
if (v) {
c = getc(f);
ungetc(c, f);
if (c != '.') {
assign_value(v, make_number(y));
}
else {
float p;
x = fscanf(f, "%f", &p);
assign_value(v, make_real(y + p));
}
}
return 0;
}
debug_message("BAD restore file format\n");
return (1);
}
}
int read_object(FILE *f, char *buff, Val *v)
{
int x,y;
char *xp;
char nam[256];
if (!v) return skip_object(f, buff);
x = fscanf(f, " %s ", nam);
if (x != 1)
{
debug_message("Bad object format (object name) in restore\n");
return 1;
}
xp = index(nam, '#');
/* if it's a clone - restore its values */
if (xp)
{
Val * tmpval;
*xp = '\0';
tmpval = caught_clone_object(nam);
if (tmpval && tmpval->type == T_OBJECT && tmpval->u.ob)
{
y = internal_restore(tmpval->u.ob, 0, f);
/* if (y)*/ assign_value(v, tmpval);
}
else return skip_object(f, buff);
return 0;
}
else
{
Obj *tmpob;
Val *tmpob2 = 0;
Shared * snam = string_copy(nam);
tmpob = find_object(snam);
free_string(snam);
if (tmpob)
{
tmpob2 = make_object(tmpob);
if (tmpob2) assign_value(v, tmpob2);
if (fscanf(f, " ") == EOF)
{
debug_message("Unbalanced object in restore\n");
return 1;
}
x = getc(f); /* suck up trailing ] */
}
else
{
/* we have an efun error will robinson (fuck!) */
return skip_object(f, buff);
return 0;
}
}
return 0;
}
/* new strings - with escape codes! */
int read_string(FILE *f, char *buff, Val *v)
{
char *s = buff;
char c = 1;
while (1)
{
*s = '\0';
switch(c = getc(f))
{
case 0:
case EOF:
if (!c || c == EOF) {
debug_message("Bad string in restore\n");
return 1;
}
case '"': /* string term */
if (v) {
assign_value(v, make_string(buff));
}
return 0;
case '\\':
c = getc(f);
switch(c) {
case '\\': goto okchar;
case '"': goto okchar;
case 'n': c = '\n'; goto okchar;
case 't': c = '\t'; goto okchar;
case '\n': goto nochar;
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9': {
int x = c - '0';
int i;
for (i = 0; i < 2; i++) {
c = getc(f);
if (!isdigit(c)) {
debug_message("Numeric escape code in restored string too short\n");
return 1;
}
x = (x << 3) + c - '0';
}
c = x & 255;
goto okchar;
}
default:
debug_message("Unrecognised escape code in restore string\n");
return 1;
}
default: goto okchar;
}
okchar:
*(s++) = c;
nochar:
;
}
return 0;
}
int read_new_string(FILE *f, char *buff, Val *v)
{
char *s = buff;
char c;
while (1)
{
*s = '\0';
c = getc(f);
if (c == EOF || !c)
{
debug_message("Bad string in restore\n");
return 1;
}
if (c == '"')
{
/* string term */
if (v) {
assign_value(v, make_string(buff));
}
return 0;
}
if (c == '\\')
{
c = getc(f);
if (!c || c == EOF) {
debug_message("Bad string in restore\n");
return 1;
}
}
*(s++) = c;
}
return 0;
}
int read_vector(FILE * f, char *buff, Val *v)
{
int x, y, num;
x = fscanf(f, " %d ", &num);
if (!v) return skip_vector(f, buff);
if (x != 1)
{
debug_message("Bad vector format (num elts) in restore\n");
return 1;
}
assign_value(v, allocate_array(num));
wipe(1);
for (y = 0; y < num; y++)
{
if (fscanf(f, " ") == EOF)
{
debug_message("Unbalanced vector in restore\n");
return 1;
}
if (read_value(f, buff, &v->u.vec->item[y])) return 1;
}
if (fscanf(f, " ") == EOF)
{
debug_message("Unbalanced vector in restore\n");
return 1;
}
x = getc(f); /* { */
if (x != '}')
{
debug_message("Expected end of vector in restore\n");
return 1;
}
return 0;
}
int skip_vector(FILE * f, char *buff)
{
char c;
int nest = 1;
int in_string = 0;
while (nest > 0)
{
c = getc(f);
if (c == EOF)
{
debug_message("Unbalanced vector (was skipping) in restore\n");
return 1;
}
if (c == '\'' && !in_string)
{
in_string = 1;
continue;
}
if (c == '"')
{
in_string = !in_string;
continue;
}
if (c == '\\' && in_string)
{
c = getc(f);
if (c == EOF)
{
debug_message("Unterminated string in skipped vector\n");
return 1;
}
continue;
}
if (in_string) continue;
if (c == '{')
{
nest++;
continue;
}
if (c == '}')
{
nest--;
continue;
}
}
return 0;
}
int skip_object(FILE * f, char *buff)
{
char c;
int nest = 1;
int in_string = 0;
/* objects shouldn't really be nested at the moment, but just in case :-) */
while (nest > 0)
{
c = getc(f);
if (c == EOF)
{
debug_message("Unbalanced vector (was skipping) in restore\n");
return 1;
}
if (c == '\'' && !in_string)
{
in_string = 1;
continue;
}
if (c == '"')
{
in_string = !in_string;
continue;
}
if (c == '\\' && in_string)
{
c = getc(f);
if (c == EOF)
{
debug_message("Unterminated string in skipped vector\n");
return 1;
}
continue;
}
if (in_string) continue;
if (c == '[')
{
nest++;
continue;
}
if (c == ']')
{
nest--;
continue;
}
}
return 0;
}
Val * save_object(Val * ob, Val * file)
{
return make_number(internal_save(ob->u.ob, file->u.string->str, NULL));
}
Val * restore_object(Val * ob, Val * file)
{
return make_number(internal_restore(ob->u.ob, file->u.string->str, NULL));
}