Logo Search packages:      
Sourcecode: jove version File versions

recover.c

/**************************************************************************
 * This program is Copyright (C) 1986-2002 by Jonathan Payne.  JOVE is    *
 * provided by Jonathan and Jovehacks without charge and without          *
 * warranty.  You may copy, modify, and/or distribute JOVE, provided that *
 * this notice is included in all the source files and documentation.     *
 **************************************************************************/

/* Recovers JOVE files after a system/editor crash.
 * Usage: recover [-d directory] [-syscrash]
 * The -syscrash option is specified in /etc/rc.  It directs recover to
 * move all the jove tmp files from tmp_dir (/tmp) to RECDIR (/usr/preserve).
 * recover -syscrash must be invoked in /etc/rc BEFORE /tmp gets cleared out.
 * (about the same place as expreserve gets invoked to save ed/vi/ex files.
 *
 * The -d option lets you specify the directory to search for tmp files when
 * the default isn't the right one.
 *
 * Look in Makefile to change the default directories.
 */

#include <stdio.h>      /* Do stdio first so it doesn't override OUR
                     definitions. */
#include "jove.h"

#define SMALLSTRSIZE    30    /* used for small buffers */

#ifndef RECOVER

int
main(argc, argv)
int   argc;
char  *argv[];
{
      printf("recovery is not implemented in this JOVE configuration.\n");
      return 1;
}

#else /* RECOVER */     /* the body is the rest of this file */

#include "temp.h"
#include "sysprocs.h"
#include "rec.h"
#include "paths.h"

#include "recover.h"

#ifdef UNIX
# include <signal.h>
# include <sys/file.h>
# include <pwd.h>
# include <time.h>
#endif

#ifdef USE_UNAME
# include <sys/utsname.h>
#endif

/* Strictly speaking, popen is not available in stdio.h in POSIX.1 or ANSI-C.
 * It is part of POSIX.2, and declared incorrectly in OSF/1, so we suppress
 * it for OSF.
 */
#ifndef     _OSF_SOURCE
extern FILE *popen proto((const char *, const char *));
#endif

#ifndef FULL_UNISTD

/* The parameter of getpwuid is widened uid_t,
 * but there no easy portable way to write this
 */
extern struct passwd *getpwuid(/*widened uid_t*/);
extern char *ctime proto((const time_t *));
extern void perror proto((const char *));

# ifdef USE_UNAME
extern int  uname proto((struct utsname *));
# endif

# ifdef USE_GETHOSTNAME
extern int  gethostname proto((const char *, size_t));
# endif

#endif /* !FULL_UNISTD */

#ifndef L_SET
# define L_SET    0
# define L_INCR   1
#endif

private char      blk_buf[JBUFSIZ];
private int nleft;
private FILE      *ptrs_fp;
private int data_fd;
private struct rec_head Header;
private long      Nchars,
      Nlines;
private char      tty[] = "/dev/tty";
private const char      *tmp_dir = TMPDIR;
private uid_t     UserID;
private bool      Verbose = NO;
private char      RecDir[] = RECDIR;

private struct file_pair {
      char  *file_data,
            *file_rec;
#define INSPECTED 01
      int   file_flags;
      struct file_pair  *file_next;
} *First = NULL;

private struct rec_entry      *buflist[100];    /* system initializes to 0 */

#ifndef F_COMPLETION
# define F_COMPLETION   /* since scandir.c is surrounded by an ifdef */
#endif

/* simpler version of one in util.c, needed by scandir.c */
UnivPtr
emalloc(size)
size_t size;
{
      register UnivPtr ptr;

      if ((ptr = malloc(size)) == NULL) {
            fprintf(stderr, "couldn't malloc(%ld)\n", (long)size);
            exit(1);
      }
      return ptr;
}

/* simpler version of one in util.c, needed by scandir.c */
UnivPtr
erealloc(ptr, size)
UnivPtr ptr;
size_t size;
{
      if ((ptr = realloc(ptr, size)) == NULL) {
            fprintf(stderr, "couldn't realloc(%ld)\n", (long)size);
            exit(1);
      }
      return ptr;
}

/* copy a string into buffer; truncate silently if too large; NUL-pad.
 * Note: buffer must be 1 larger than n to fit NUL!
 * Duplicated from util.c: needed by scandir.c
 */
void
null_ncpy(to, from, n)
char  *to;
const char  *from;
size_t      n;
{
      (void) strncpy(to, from, n);
      to[n] = '\0';
}

#define complain printf /* kludge! needed by scandir.c */
#include "scandir.c"    /* to get dirent simulation and jscandir */

/* Get a line at `tl' in the tmp file into `buf' which should be LBSIZE
 * long.
 */

private char      *getblock proto((daddr atl));

void
getline(tl, buf)
daddr tl;
char  *buf;
{
      register char     *bp,
                  *lp;
      register int      nl;

      lp = buf;
      bp = getblock(tl);
      nl = nleft;

      while ((*lp++ = *bp++) != '\0') {
            if (--nl == 0) {
                  /* oops: line didn't end within block: fake it */
                  *lp++ = '\0';
                  break;
            }
      }
}

private char *
getblock(atl)
daddr atl;
{
      int   bno,
            off;
      static int  curblock = -1;

      bno = da_to_bno(atl);
      off = da_to_off(atl);
      nleft = JBUFSIZ - off;

      if (bno != curblock) {
            lseek(data_fd, (long) bno * JBUFSIZ, L_SET);
            read(data_fd, (UnivPtr)blk_buf, (size_t)JBUFSIZ);
            curblock = bno;
      }
      return blk_buf + off;
}

char *
copystr(s)
const char  *s;
{
      char  *str;

      str = malloc((size_t) (strlen(s) + 1));
      if (str == NULL) {
            fprintf(stderr, "recover: cannot malloc for copystr.\n");
            exit(-1);
      }
      strcpy(str, s);

      return str;
}

private const char      *CurDir;

/* Scan the DIRNAME directory for jove tmp files, and make a linked list
 * out of them.
 */

private bool      add_name proto((char *));

private void
get_files(dirname)
const char  *dirname;
{
      char  **nmptr;
      int   nentries;

      /* first, free any previous entries */
      while (First != NULL) {
            struct file_pair  *p = First;

            First = p->file_next;
            free((UnivPtr) p);
      }

      CurDir = dirname;
      nentries = jscandir(dirname, &nmptr, add_name,
            (int (*) ptrproto((UnivConstPtr, UnivConstPtr)))NULL);
      if (nentries != -1)
            freedir(&nmptr, nentries);
}

private bool
add_name(fname)
char *fname;
{
      char  dfile[FILESIZE],
            rfile[FILESIZE];
      struct file_pair  *fp;
      struct rec_head         header;
      int   fd;
      static const char jrecstr[] = "jrec";

      if (strncmp(fname, jrecstr, sizeof(jrecstr)-1) != 0)
            return NO;

      /* If we get here, we found a "recover" tmp file, so now
       * we look for the corresponding "data" tmp file.  First,
       * though, we check to see whether there is anything in
       * the "recover" file.  If it's 0 length, there's no point
       * in saving its name.
       */
      (void) sprintf(rfile, "%s/%s", CurDir, fname);
      if ((fd = open(rfile, O_RDONLY | O_BINARY)) != -1) {
            if (read(fd, (UnivPtr) &header, sizeof header) != sizeof header) {
                  close(fd);
                  fprintf(stderr, "recover: could not read complete header from %s, skipping\n", rfile);
                  return NO;
            }
            if (header.RecMagic != RECMAGIC) {
                  close(fd);
                  fprintf(stderr, "recover: %s is not from this version of JOVE, skipping\n", rfile);
                  return NO;
            }
            close(fd);
      }
      (void) sprintf(dfile, "%s/%s", CurDir, header.TmpFileName);
      if (access(dfile, F_OK) != 0) {
            fprintf(stderr, "recover: can't find the data file `%s' for %s\n", header.TmpFileName, rfile);
#ifdef NEVER
            /*
             * MM: I don't think it's a good idea to delete the files
             * because access() failed.  We should probably ask the user
             * if it is ok to delete the file!
             */
            fprintf(stderr, "so deleting...\n");
            (void) unlink(rfile);
#endif
            return NO;
      }
      /* If we get here, we've found both files, so we put them
       * in the list.
       */
      fp = (struct file_pair *) malloc(sizeof *fp);
      if (fp == NULL) {
            fprintf(stderr, "recover: cannot malloc for file_pair.\n");
            exit(-1);
      }
      fp->file_data = copystr(dfile);
      fp->file_rec = copystr(rfile);
      fp->file_flags = 0;
      fp->file_next = First;
      First = fp;

      return YES;
}

private void
options()
{
      printf("Options are:\n");
      printf("    ?           list options.\n");
      printf("    get         get a buffer to a file.\n");
      printf("    list        list known buffers.\n");
      printf("    print       print a buffer to terminal.\n");
      printf("    quit        quit and delete jove tmp files.\n");
      printf("    restore           restore all buffers.\n");
}

/* Returns a legitimate buffer # */

private void      tellme proto((const char *, char *, size_t)),
      list proto((void));

private struct rec_entry **
getsrc()
{
      char  name[FILESIZE];
      int   number;

      for (;;) {
            tellme("Which buffer ('?' for list)? ", name, sizeof(name));
            if (name[0] == '?')
                  list();
            else if (name[0] == '\0')
                  return NULL;
            else if ((number = atoi(name)) > 0 && number <= Header.Nbuffers)
                  return &buflist[number];
            else {
                  int   i;

                  for (i = 1; i <= Header.Nbuffers; i++)
                        if (strcmp(buflist[i]->r_bname, name) == 0)
                              return &buflist[i];
                  printf("%s: unknown buffer.\n", name);
            }
      }
}

/* Get a destination file name. */

private char *
getdest()
{
      static char filebuf[FILESIZE];

      tellme("Output file: ", filebuf, sizeof(filebuf));
      if (filebuf[0] == '\0')
            return NULL;
      return filebuf;
}

#include "jctype.h"

private char *
readword(buf, buflen)
char  *buf;
size_t      buflen;
{
      int   c;
      char  *bp = buf,
            *ep = buf + buflen - 1;

      do ; while (strchr(" \t\n", c = getchar()) != NULL);

      for (;;) {
            if (c == EOF)
                  exit(0);
            if (strchr(" \t\n", c) != NULL)
                  break;

            if (bp == ep) {
                  *bp = '\0';
                  fprintf(stderr, "%lu byte buffer too small for word `%s'",
                        (unsigned long) buflen, buf);
                  exit(0);
            }
            *bp++ = c;
            c = getchar();
      }
      *bp = '\0';

      return buf;
}

private void
tellme(quest, answer, anslen)
const char  *quest;
char  *answer;
size_t      anslen;
{
      printf("%s", quest);
      fflush(stdout);
      readword(answer, anslen);
}

private jmp_buf   int_env;

private SIGRESTYPE
catch(junk)
int   UNUSED(junk);
{
      longjmp(int_env, 1);
      /*NOTREACHED*/
}

private void      get proto((struct rec_entry **src, char *dest));

private void
restore()
{
      register int      i;
      char  tofile[FILESIZE],
            answer[SMALLSTRSIZE];
      int   nrecovered = 0;

      for (i = 1; i <= Header.Nbuffers; i++) {
            (void) sprintf(tofile, "#%s", buflist[i]->r_bname);
tryagain:
            printf("Restoring %s to %s, okay?", buflist[i]->r_bname,
                                         tofile);
            tellme(" ", answer, sizeof(answer));
            switch (answer[0]) {
            case 'y':
                  break;

            case 'n':
                  continue;

            default:
                  tellme("What file should I use instead? ", tofile,
                         sizeof(tofile));
                  goto tryagain;
            }
            get(&buflist[i], tofile);
            nrecovered += 1;
      }
      printf("Recovered %d buffers.\n", nrecovered);
}

private void      dump_file proto((int which, FILE *out));

private void
get(src, dest)
struct rec_entry  **src;
char  *dest;
{
      FILE  *volatile outfile;      /* "volatile" to preserve outfile across setjmp */

      if (src == NULL || dest == NULL)
            return;

      if (dest == tty)
            outfile = stdout;
      else {
            if ((outfile = fopen(dest, "wb")) == NULL) {
                  printf("recover: cannot create %s.\n", dest);
                  (void) signal(SIGINT, SIG_DFL);
                  return;
            }
            printf("\"%s\"", dest);
      }
      if (setjmp(int_env) == 0) {
            (void) signal(SIGINT, catch);
            dump_file(src - buflist, outfile);
      } else {
            printf("\nAborted!\n");
      }
      (void) signal(SIGINT, SIG_DFL);
      if (dest != tty) {
            fclose(outfile);
            printf(" %ld lines, %ld characters.\n", Nlines, Nchars);
      }
}

private char **
scanvec(args, str)
register char     **args,
            *str;
{
      while (*args) {
            if (strcmp(*args, str) == 0)
                  return args;
            args += 1;
      }
      return NULL;
}

private void
read_rec(recptr)
struct rec_entry  *recptr;
{
      if (fread((UnivPtr) recptr, sizeof *recptr, (size_t)1, ptrs_fp) != 1)
            fprintf(stderr, "recover: cannot read record.\n");
}

private void
seekto(which)
int   which;
{
      long  offset;
      int   i;

      offset = sizeof (Header) + (Header.Nbuffers * sizeof (struct rec_entry));
      for (i = 1; i < which; i++)
            offset += buflist[i]->r_nlines * sizeof (daddr);
      fseek(ptrs_fp, offset, L_SET);
}

private void
makblist()
{
      int   i;

      fseek(ptrs_fp, (long) sizeof (Header), L_SET);
      for (i = 1; i <= Header.Nbuffers; i++) {
            if (buflist[i] == NULL) {
                  buflist[i] = (struct rec_entry *) malloc (sizeof (struct rec_entry));
                  if (buflist[i] == NULL) {
                        fprintf(stderr, "recover: cannot malloc for makblist.\n");
                        exit(-1);
                  }
            }
            read_rec(buflist[i]);
      }
      while (buflist[i]) {
            free((UnivPtr) buflist[i]);
            buflist[i] = NULL;
            i += 1;
      }
}

private daddr
getaddr(fp)
register FILE     *fp;
{
      register int      nchars = sizeof (daddr);
      daddr addr;
      register char     *cp = (char *) &addr;

      while (--nchars >= 0)
            *cp++ = getc(fp);

      return addr;
}

private void
dump_file(which, out)
int   which;
FILE  *out;
{
      register int      nlines;
      register daddr    addr;
      char  buf[JBUFSIZ];

      seekto(which);
      nlines = buflist[which]->r_nlines;
      Nchars = Nlines = 0L;
      while (--nlines >= 0) {
            addr = getaddr(ptrs_fp);
            getline(addr, buf);
            Nlines += 1;
            Nchars += 1 + strlen(buf);
            fputs(buf, out);
            if (nlines > 0)
                  fputc('\n', out);
      }
}

/* List all the buffers. */

private void
list()
{
      int   i;

      for (i = 1; i <= Header.Nbuffers; i++)
            printf("%d) buffer %s  \"%s\" (%d lines)\n", i,
                  buflist[i]->r_bname,
                  buflist[i]->r_fname,
                  buflist[i]->r_nlines);
}

private void      ask_del proto((const char *prompt, struct file_pair *fp));

private int
doit(fp)
struct file_pair  *fp;
{
      char  answer[SMALLSTRSIZE];
      char  *datafile = fp->file_data,
            *pntrfile = fp->file_rec;

      ptrs_fp = fopen(pntrfile, "rb");
      if (ptrs_fp == NULL) {
            if (Verbose)
                  fprintf(stderr, "recover: cannot read rec file (%s).\n", pntrfile);
            return 0;
      }
      fread((UnivPtr) &Header, sizeof Header, (size_t)1, ptrs_fp);
      if (Header.Uid != UserID)
            return 0;

      /* Ask about JOVE's that are still running ... */
      if (kill(Header.Pid, 0) == 0)
            return 0;

      if (Header.Nbuffers == 0) {
            printf("There are no modified buffers in %s; should I delete the tmp file?", pntrfile);
            ask_del(" ", fp);
            return 1;
      }

      if (Header.Nbuffers < 0) {
            fprintf(stderr, "recover: %s doesn't look like a jove file.\n", pntrfile);
            ask_del("Should I delete it? ", fp);
            return 1;   /* We'll, we sort of found something. */
      }
      printf("Found %d buffer%s last updated: %s",
            Header.Nbuffers,
            Header.Nbuffers != 1 ? "s" : "",
            ctime(&Header.UpdTime));
      data_fd = open(datafile, O_RDONLY | O_BINARY);
      if (data_fd == -1) {
            fprintf(stderr, "recover: but I can't read the data file (%s).\n", datafile);
            ask_del("Should I delete the tmp files? ", fp);
            return 1;
      }
      makblist();
      list();

      for (;;) {
            tellme("(Type '?' for options): ", answer, sizeof(answer));
            switch (answer[0]) {
            case '\0':
                  continue;

            case '?':
                  options();
                  break;

            case 'l':
                  list();
                  break;

            case 'p':
                  get(getsrc(), tty);
                  break;

            case 'q':
                  ask_del("Shall I delete the tmp files? ", fp);
                  return 1;

            case 'g':
                {
                  /* So it asks for src first. */
                  char  *dest;
                  struct rec_entry  **src;

                  if ((src = getsrc()) == NULL)
                        break;

                  dest = getdest();
                  get(src, dest);
                  break;
                }

            case 'r':
                  restore();
                  break;

            default:
                  printf("I don't know how to \"%s\"!\n", answer);
                  break;
            }
      }
}

private void      del_files proto((struct file_pair *fp));

private void
ask_del(prompt, fp)
const char  *prompt;
struct file_pair  *fp;
{
      char  yorn[SMALLSTRSIZE];

      tellme(prompt, yorn, sizeof(yorn));
      if (yorn[0] == 'y')
            del_files(fp);
}

private void
del_files(fp)
struct file_pair  *fp;
{
      (void) unlink(fp->file_data);
      (void) unlink(fp->file_rec);
}


private const char *
hname()
{
      const char *p = "unknown";
#ifdef USE_UNAME
      static struct utsname mach;

      if (uname(&mach) >= 0)
            p = mach.nodename;
#endif
#ifdef USE_GETHOSTNAME
      static char mach[BUFSIZ];

      if (gethostname(mach, sizeof(mach)) >= 0)
            p = mach;
#endif
      return p;
}

private void
MailUser(rec)
struct rec_head *rec;
{
      char mail_cmd[BUFSIZ];
      char *last_update;
      const char *buf_string;
      FILE *mail_pipe;
      struct passwd *pw;

      if ((pw = getpwuid(rec->Uid))== NULL)
            return;

      last_update = ctime(&(rec->UpdTime));
      /* Start up mail */
      sprintf(mail_cmd, "/bin/mail %s", pw->pw_name);
      setuid(getuid());
      if ((mail_pipe = popen(mail_cmd, "w")) == NULL)
            return;

      setbuf(mail_pipe, mail_cmd);
      /* Let's be grammatically correct! */
      if (rec->Nbuffers == 1)
            buf_string = "buffer";
      else
            buf_string = "buffers";
      fprintf(mail_pipe, "Subject: System crash\n");
      fprintf(mail_pipe, " \n");
      fprintf(mail_pipe, "Jove saved %d %s when the system \"%s\"\n",
       rec->Nbuffers, buf_string, hname());
      fprintf(mail_pipe, "crashed on %s\n\n", last_update);
      fprintf(mail_pipe, "You can retrieve the %s using Jove's -r\n",
       buf_string);
      fprintf(mail_pipe, "(recover option) i.e. give the command.\n");
      fprintf(mail_pipe, "\tjove -r\n");
      fprintf(mail_pipe, "See the Jove manual for more details\n");
      pclose(mail_pipe);
}


private void
savetmps()
{
      struct file_pair  *fp;
      wait_status_t     status;
      pid_t pid;
      int   fd;
      struct rec_head         header;
      char  buf[BUFSIZ];
      char  *fname;
      struct stat       stbuf;

      if (strcmp(tmp_dir, RecDir) == 0)
            return;           /* Files are moved to the same place. */

      get_files(tmp_dir);
      for (fp = First; fp != NULL; fp = fp->file_next) {
            if (stat(fp->file_data, &stbuf) < 0) {
                  perror("recover: stat failed.");
                  continue;
            }
            switch (pid = fork()) {
            case -1:
                  fprintf(stderr, "recover: can't fork\n!");
                  exit(-1);
                  /*NOTREACHED*/

            case 0:
                  fprintf(stderr, "Recovering: %s, %s\n", fp->file_data,
                   fp->file_rec);
                  if ((fd = open(fp->file_rec, O_RDONLY | O_BINARY)) != -1) {
                        if ((read(fd, (UnivPtr) &header, sizeof header) != sizeof header)) {
                              close(fd);
                              return;
                        } else
                              close(fd);
                  }
                  MailUser(&header);
                  execl("/bin/mv", "mv", fp->file_data, fp->file_rec,
                          RecDir, (char *)NULL);
                  fprintf(stderr, "recover: cannot execl /bin/mv.\n");
                  exit(-1);
                  /*NOTREACHED*/

            default:
                  do ; while (wait(&status) != pid);
                  if (WIFSIGNALED(status))
                        fprintf(stderr, "recover: copy terminated by signal %d\n.\n", WTERMSIG(status));
                  if (WIFEXITED(status))
                        fprintf(stderr, "recover: copy exited with %d.\n", WEXITSTATUS(status));
                  fname = fp->file_data + strlen(tmp_dir);
                  strcpy(buf, RecDir);
                  strcat(buf, fname);
                  if (chown(buf, stbuf.st_uid, stbuf.st_gid) != 0)
                        perror("recover: chown failed.");
                  fname = fp->file_rec + strlen(tmp_dir);
                  strcpy(buf, RecDir);
                  strcat(buf, fname);
                  if (chown(buf, stbuf.st_uid, stbuf.st_gid) != 0)
                        perror("recover: chown failed.");
            }
      }
}

private int
lookup(dir)
const char  *dir;
{
      struct file_pair  *fp;
      int   nfound = 0;

      printf("Checking %s ...\n", dir);
      get_files(dir);
      for (fp = First; fp != NULL; fp = fp->file_next) {
            nfound += doit(fp);
            if (ptrs_fp)
                  (void) fclose(ptrs_fp);
            if (data_fd > 0)
                  (void) close(data_fd);
      }
      return nfound;
}

int
main(argc, argv)
int   UNUSED(argc);
char  *argv[];
{
      int   nfound;
      char  **argvp;

      UserID = getuid();

      /* override <TMPDIR> with $TMPDIR, if any */
      {
            char  *cp = getenv("TMPDIR");

            if (cp != NULL)
                  tmp_dir = cp;
      }

      if (scanvec(argv, "-help")) {
            printf("recover: usage: recover [-d directory] [-syscrash]\n\n");
            printf("Use \"jove -r\" after JOVE has died for some unknown reason.\n\n");
            printf("Use \"%s/recover -syscrash\"\n", LIBDIR);
            printf("\twhen the system is in the process of rebooting.\n");
            printf("\tThis is done automatically at reboot time and\n");
            printf("\tso most of you don't have to worry about that.\n\n");
            printf("Use \"recover -d directory\"\n");
            printf("\twhen the tmp files are stored in 'directory'\n");
            printf("\tinstead of in the default one (%s).\n\n", tmp_dir);
            exit(0);
      }
      if (scanvec(argv, "-v"))
            Verbose = YES;
      if ((argvp = scanvec(argv, "-d")) != NULL)
            tmp_dir = argvp[1];
      if (scanvec(argv, "-syscrash")) {
            printf("Recovering jove files ... ");
            savetmps();
            printf("Done.\n");
            exit(0);
      }
      if ((argvp = scanvec(argv, "-uid")) != NULL)
            UserID = atoi(argvp[1]);
      /* Check default directory */
      nfound = lookup(tmp_dir);
      /* Check whether anything was saved when system died? */
      if (strcmp(tmp_dir, RecDir) != 0)
            nfound += lookup(RecDir);
      if (nfound == 0)
            printf("There's nothing to recover.\n");
      return 0;
}

#endif /* RECOVER */

Generated by  Doxygen 1.6.0   Back to index