/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is	*
 * provided to you without charge, and with no warranty.  You may give	*
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.						*
 ************************************************************************/

/* Recovers JOVE files after a system/editor crash.
   Usage: recover [-v] [-user name] [-d directory] [-check]
#ifdef REC_DIR
	  recover [-v] -preserve [-nomail]

   The -preserve option is specified in /etc/rc.  It directs recover to
   move all the jove tmp files from TMP_DIR (usually /tmp) to REC_DIR
   (usually /usr/preserve).
   recover -preserve must be invoked in /ect/rc BEFORE /tmp gets cleared out,
   (about the same place as expreserve gets invoked to save ed/vi/ex files)
   but AFTER name daemons like Yellow Pages have started up (since users
   are notified by mail (unless the -nomail option is given).
   This may involve moving the cleanup of /tmp around in /etc/rc.
   The old name of this option, -syscrash, is still supported for
   compatibility's sake, but this support may go away in the future.
#endif
   The -d option lets you specify the directory to search for tmp files when
   the default isn't the right one.

   The -check option checks if you have anything to recover and displays
   a message if so.  This is useful in your shell startup file.

   Look in Makefile to change the default directories. */

/* TODO: merge -preserve mail messages to the same user.
   (should be rather straightforward now file-pair list is sorted on UID.
 */

#include <stdio.h>	/* Do stdio first so it doesn't override OUR
			   definitions. */
#undef EOF
#undef NULL
#undef PATH_MAX

#define NOT_JOVE	/* we're not part of JOVE itself. */
#include "jove.h"

RCS("$Id: recover.c,v 14.31.0.9 1994/01/31 14:31:08 tom Exp tom $")

#include "io.h"
#include "rec.h"
#include "temp.h"
#include "version.h"
#undef signal
#include <signal.h>

#ifndef PASSWD
#   if unix || vms
#	define PASSWD
#   endif
#endif

#ifdef PASSWD
#   include <pwd.h>
#   if !__POSIX__
extern struct passwd	*getpwuid __(( int /*uid_t*/ _(uid) )),
			*getpwnam __(( const char *_(username) ));
#   endif
#endif

#define READDIR_IMPLEMENTATION
#include "readdir.h"

#ifndef TMP_DIR
#   define TMP_DIR	TmpFilePath
#endif

#ifdef REC_DIR
#   if !unix	/* -preserve option currently only supported on UNIX systems */
#	undef REC_DIR
#   endif
#endif

char	*ctime __(( const time_t *_(timep) ));

char	blk_buf[BLKSIZ];
int	nleft;
FILE	*ptrs_fp;
int	data_fd;
private struct rec_head Header;
long	Nchars,
	Nlines;
int	UserID,
	Verbose = 0,
	l_tempfile;		/* length of constant prefix of [dp]_tempfile */

struct file_pair {
	char	*file_data,
		*file_rec;
#define INSPECTED	01
	int	file_flags;
	time_t	file_updtime;
	int	file_uid;
	struct file_pair	*file_next;
} *First = 0;

struct rec_entry	**buflist = 0;

void_*
emalloc(size)
size_t size;
{
	register void_* p = malloc(size);

	if (p == NULL) {
		printf("recover: out of memory\n");
		exit(-1);
	}
	return p;
}

#ifdef ATARIST
#   define AltSlash	0	/* don't want Alternate path sep. here */

#ifndef __GNUC__		/* Avoid clash with gnulib */
private void	(*org_pterm)__(( void )) = (void (*)()) -1;
private sig_tp	(*sigint_proc)__(( int _(sig) )) = SIG_DFL;

#define PTERM_EXC	0x102

private void do_sigint __(( void ));
private void
do_sigint()
{
	int	dummy;

	Super(&dummy);		/* back to user mode */
	(*sigint_proc)(SIGINT);
	signal(SIGINT, SIG_DFL);	/* re-install system's Pterm handler */
}

/*
 * Emulate signal()
 * this only handles SIGINT requests; others are ignored.
 * SIGINT signal MUST be reset to SIG_DFL before exiting.
 */
sig_tp (*signal(sig, proc))__(( int _(sig) ))
int	sig;
sig_tp	(*proc)__(( int _(sig) ));
{
	register sig_tp	(*prev)__(( int _(sig) ));

	switch (sig) {
	case SIGINT:
		prev = sigint_proc;
		sigint_proc = proc;
		proc = Setexc(PTERM_EXC, (proc == SIG_IGN || proc == SIG_DFL) ?
							org_pterm : do_sigint);
		if (org_pterm == (void (*)()) -1)
			org_pterm = (void (*)__(( void ))) proc;
		break;

	default:
		prev = SIG_DFL;
		break;
	}
	return prev;
}
#endif /* __GNUC__ */
#endif /* ATARIST */

private int	curblock = -1;
private int	lastblock;

char *getblock __(( disk_line _(atl) ));
char *
getblock(atl)
disk_line	atl;
{
	int	bno,
		off;

	bno = daddr2bno(atl);
	off = daddr2off(atl);
	nleft = BLKSIZ - off;

	if ((unsigned) bno >= lastblock)	/* guard against bogus values */
		return "[Beyond EOF]";

	if (bno != curblock) {
		lseek(data_fd, (long) bno * BLKSIZ, SEEK_SET);
		read(data_fd, blk_buf, BLKSIZ);
		curblock = bno;
	}
	return blk_buf + off;
}

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

#if 0
/* we don't need this since JOVE does not address partial lines anymore */
void getline __(( disk_line _(tl), char *_(buf) ));
void
getline(tl, buf)
disk_line	tl;
char	*buf;
{
	register char	*bp,
			*lp;
	register int	nl;

	lp = buf;
	bp = getblock(tl >> 1);
	nl = nleft;
	tl = blk_round(tl);

	while (*lp++ = *bp++) {
		if (--nl == 0) {
			tl = forward_block(tl);
			bp = getblock(tl >> 1);
			nl = nleft;
			lp = buf;
		}
	}
}
#endif

char *
copystr(s)
const char	*s;
{
	return strcpy(emalloc(strlen(s) + 1), s);
}

char *
make_filename(buf, path, name)
char		*buf;
const char	*path, *name;
{
	register const char	*s = path;
	register char		*d = buf;

	while(*d++ = *s++)
		;
	if (--d > buf && !ISDIRSEP(d[-1]))
		*d++ = SLASH;
	s = name;
	while (*d++ = *s++)
		;
	return buf;
}

char *
basename(f)
register const char	*f;
{
	register const char	*cp;
	register char		c;
#ifdef DOS
	if (f[0] && f[1] == ':')
		f += 2;
#endif
	for (cp = f; (c = *cp++); ) {
#if vms		/* handle VMS-style filespec. */
		if (c == ']' || c == ':' || c == '>')
			f = cp;
#endif
		if (ISDIRSEP(c))
			f = cp;
	}
	return (char *) f;
}

#if vms	/* for vms2ux */
char *
appcpy(t, f)
register char		*t;
register const char 	*f;
{
	while (*t++ = *f++)
		;
	return --t;
}
#endif

#ifdef BACKUPFILES
/*
 * make a backup copy of the file, if it exists.
 * (do a physical copy so that original keeps the same i-node)
 * set access/modified times of backup copy to those of original.
 * use iobuff as intermediate buffer.
 * For DOS systems, do a rename since there is no I-node to preserve
 * (and it is faster)
 */
int file_backup __(( const char *_(fname) ));
int
file_backup(fname)
const char *fname;
{
	char		*strcpy();
	register int	i;
	register int	fd1,
			fd2;
	char		backup[FILESIZE];
	struct stat	stbuf;

	sprintf(basename(strcpy(backup, fname)), "#%s", basename(fname));

#ifdef DOS
	unlink(backup);
	if ((i = rename(fname, backup)) < 0 && errno == ENOENT)
		return 0;

	printf("Backup to %s...", backup);
#else
	if ((fd1 = open(fname, O_RDONLY)) < 0)
		return 0;

	fstat(fd1, &stbuf);	/* should not fail */

	printf("Backup to %s...", backup);
	fflush(stdout);
	if ((fd2 = creat(backup, stbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO))) >= 0) {
		char	iobuff[BUFSIZ];
		time_t	times[2];

		while ((i = read(fd1, iobuff, BUFSIZ)) > 0 &&
			write(fd2, iobuff, i) == i);

#ifdef BSD4_2
		fsync(fd2);
#endif
		close(fd2);

		/* now cheat -- set times of backup to that of original */

		times[0] = stbuf.st_atime;
		times[1] = stbuf.st_mtime;
		utime(backup, times);
	}

	close(fd1);
#endif /* DOS */

	if (i != 0) {
		printf((i < 0) ? " [Can't create]" : " [Incomplete]");
	}
	printf("\n");

	return i;
}
#endif /* BACKUPFILES */

/* cleanup invalid recovery file pairs */

void cleanup __(( const char *_(message),
		  const char *_(rfile),  const char *_(dfile) ));
void
cleanup(message, rfile, dfile)
const char	*message, *rfile, *dfile;
{
	if (Verbose)
		fprintf(stderr, "%s \"%s\".\nso deleting...\n", message, rfile);
	unlink(rfile);
	unlink(dfile);
}

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

const char	*CurDir;

int add_name __(( struct dirent *_(dp) ));
int
add_name(dp)
register struct dirent	*dp;
{
	char	dfile[FILESIZE],
		rfile[FILESIZE];
	register struct file_pair	*fp;
	register int	fd;

	if (strncmp(dp->d_name, p_tempfile, l_tempfile) != 0 &&
	    strncmp(dp->d_name, d_tempfile, l_tempfile) != 0)
		return 0;
	/*
	 * If we get here, we found a "recover" or "data" tmp file, so now we
	 * look for the corresponding "data" resp. "recover" 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.
	 */
	make_filename(rfile, CurDir, strncpy(dp->d_name, p_tempfile, l_tempfile));
	make_filename(dfile, CurDir, strncpy(dp->d_name, d_tempfile, l_tempfile));

	if ((fd = open(rfile, O_RDONLY)) >= 0) {
		if ((read(fd, (char *) &Header, (int) sizeof Header) != sizeof Header)) {
			close(fd);
			cleanup("Empty recovery file", rfile, dfile);
			return 0;
		}
		close(fd);
	}
	else if (access(rfile, F_OK) != 0) {
		/* [TRH 17-Dec-90, revised 26-Jun-91]
		 * Try to find out if this file belongs to an active JOVE.
		 * We don't have its pid (since that would be in the "recover"
		 * file -- that doesn't exist), so we have to make do
		 * with this heuristic:  only delete if we (pretend to) own
		 * the file and it has not been accessed during the past hour.
		 */
		struct stat	stbuf;

		if (stat(dfile, &stbuf) == 0 &&
		    stbuf.st_uid == UserID &&
		    stbuf.st_atime < time(NULL) - 60*60)
			cleanup("Can't find the recovery file for", dfile, rfile);
		return 0;
	}
	if (access(dfile, R_OK) != 0) {
		/*
		 * Either we don't have permission to read the "data" file
		 * (and we should leave it alone), or it does not exist
		 * (so we can ditch the accompanying "recover" file.)
		 */
		if (access(dfile, F_OK) != 0)
			cleanup("Can't find the data file for", rfile, dfile);
		return 0;
	}
	/*
	 * If we get here, we've found both files, so we put them in the list,
	 * IF they are not already on it.
	 */
	if (fp = First) do {
		if (strcmp(fp->file_data, dfile) == 0)
			return 0;
	} while (fp = fp->file_next);

	fp = (struct file_pair *) emalloc (sizeof *fp);
	fp->file_data = copystr(dfile);
	fp->file_rec = copystr(rfile);
	fp->file_flags = 0;
	fp->file_updtime = Header.UpdTime;
	fp->file_uid = Header.Uid;

	/* Sort file_pairs by used id. and update time (most recent first). */
    {
	register struct file_pair	*next, **at;

	for (at = &First; next = (*at); at = &next->file_next) {
		if (fp->file_uid <= next->file_uid) {
			if (fp->file_uid < next->file_uid ||
			    fp->file_updtime >= next->file_updtime)
				break;
		}
	}
	fp->file_next = next;
	(*at) = fp;
    }
	return 1;
}

void get_files __(( const char *_(dirname) ));
void
get_files(dirname)
const char	*dirname;
{
	DIR		*dirp;
	struct dirent	*dentry;

	CurDir = dirname;

	if ((dirp = opendir(dirname)) == NULL)
		return;

	while ((dentry = readdir(dirp)) != NULL)
		add_name(dentry);

	closedir(dirp);
}

#ifdef REC_DIR
void free_files __(( void ));
void
free_files()
{
	register struct file_pair	*fp;

	while (fp = First) {
		First = fp->file_next;
		free(fp->file_data);
		free(fp->file_rec);
		free(fp);
	}
}
#endif

private int	lastc = '\n';

char *readword __(( char *_(buf) ));
char *
readword(buf)
char	*buf;
{
	int	c;
	char	*bp = buf;

	while (index(" \t", c = getchar())) ;

	do {
		if (index(" \t\n", c))
			break;
		*bp++ = c;
	} while ((c = getchar()) != EOF);
	*bp = 0;

	lastc = c;
	return buf;
}

void tellme __(( const char *_(quest), char *_(answer) ));
void
tellme(quest, answer)
const char	*quest;
char		*answer;
{
	if (lastc == '\n') {
		printf("%s", quest);
		fflush(stdout);
	}
	readword(answer);
}

/* list all the options. */

void options __(( void ));
void
options()
{
	printf("JOVE recover (%s)\n", VERSION_SYSTEM_DATE_AND_GURU);
	puts("Options are:\n\
	?		list options.\n\
	g)et		get a buffer to a file.\n\
	l)ist		list known buffers.\n\
	p)rint		print a buffer to terminal.\n\
	q)uit		quit and delete jove tmp files.\n\
	r)estore	restore all buffers.");
}

/* List all the buffers. */

void list __(( FILE *_(outfile) ));
void
list(outfile)
register FILE	*outfile;
{
	register int	i;

	for (i = 1; i <= Header.Nbuffers; i++) {
		register struct rec_entry *r = buflist[i];

		fprintf(outfile, "%2d) buffer %-14s \"%s\" (%d line%s)\n", i,
		       r->r_bname,
		       r->r_fname[0] ? r->r_fname : "[No file]",
		       r->r_nlines,
		       r->r_nlines == 1 ? "" : "s");
	}
}

/* Returns a legitimate buffer # */

struct rec_entry **getsrc __(( void ));
struct rec_entry **
getsrc()
{
	char		name[BNAMESIZE];
	register int	number;

	for (;;) {
		tellme("Which buffer ('?' for list)? ", name);
		if (name[0] == '?')
			list(stdout);
		else if (name[0] == '\0')
			return 0;
		else if ((number = atoi(name)) > 0 && number <= Header.Nbuffers)
			return &buflist[number];
		else {
			register int	i;
			register int	len = strlen(name);
			number = 0;
			for (i = 1; i <= Header.Nbuffers; i++)
				if (strncmp(buflist[i]->r_bname, name, len) == 0) {
					if (buflist[i]->r_bname[len] == '\0')
						return &buflist[i];
					if (number == 0)
						number = i;
					else {
						number = -1;
						break;
					}
				}
			if (number > 0)
				return &buflist[i];

			printf((number < 0) ? "%s: supply more characters.\n" :
					      "%s: unknown buffer.\n", name);
		}
	}
}

/* Get a destination file name. */

private const char *getdest __(( const char *_(deflt) ));
private const char *
getdest(deflt)
const char	*deflt;
{
	static char	filebuf[FILESIZE];
	char		prompt[FILESIZE+20];

	strcpy(prompt, "Output file: ");
	if (deflt)
		if (deflt[0])
			sprintf(prompt + strlen(prompt), "(default %s) ", deflt);
		else
			deflt = 0;
	tellme(prompt, filebuf);
	if (filebuf[0] == '\0')
		return deflt;
	return filebuf;
}

/* Print the specified file to standard output. */

jmp_buf int_env;

sig_tp catch_sig __(( int _(sig) ));
sig_tp
catch_sig(sig)
{
	longjmp(int_env, sig);
}

char *const *scanvec __(( char *const *_(args), const char *_(str) ));
char *const *
scanvec(args, str)
register char *const	*args;
register const char	*str;
{
	while (*args) {
		if (strcmp(*args, str) == 0)
			return args;
		args++;
	}
	return 0;
}

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

/* [TRH] store offsets to records for more efficient seeks.
   This assumes that current record file is not actively used by a
   runnning JOVE (we check that in do_it). */

private long	*seeklist = 0;

void seekto __(( int _(which) ));
void
seekto(which)
{
	if (seeklist == NULL) {
		printf("(seekto) seeklist not set!\n");
		return;
	}
	if (which > Header.Nbuffers) {
		printf("(seekto) bad record %d (max %d)!\n",
		       which, Header.Nbuffers);
		return;
	}
	if (fseek(ptrs_fp, seeklist[which], SEEK_SET) != 0)
		printf("(seekto) improper fseek! (record %d)\n", which);
}

void makblist __(( void ));
void
makblist()
{
	register int	i;
	register struct rec_entry *rec;
	register long	where = sizeof Header,
			rec_size;

	fseek(ptrs_fp, where, SEEK_SET);	/* just to be sure */

	if (buflist) {
		for (i = 1; buflist[i]; i++)
			free(buflist[i]);
		free(buflist);
		buflist = NULL;
	}
	buflist = (struct rec_entry **) emalloc((Header.Nbuffers + 2) * sizeof(struct rec_entry *));

	if (seeklist) {
		free(seeklist);
		seeklist = NULL;
	}
	seeklist = (long *) emalloc((Header.Nbuffers + 2) * sizeof(long));

	for (i = 1; i <= Header.Nbuffers; i++) {
		rec = (struct rec_entry *) emalloc(sizeof(struct rec_entry));
		buflist[i] = rec;
		seeklist[i] = where;
		read_rec(rec);
		rec_size = (long) rec->r_nlines * sizeof(disk_line);
		where += sizeof(struct rec_entry) + rec_size;
		if (fseek(ptrs_fp, rec_size, SEEK_CUR) != 0) {
			printf("(makblist) improper fseek! (record %d)\n", i);
			Header.Nbuffers = i;	/* cheat! */
		}
	}
	buflist[i] = NULL;
}

disk_line getaddr __(( FILE *_(fp) ));
disk_line
getaddr(fp)
register FILE	*fp;
{
	register int	nchars = sizeof (disk_line);
	disk_line	addr;
	register char	*cp = (char *) &addr;

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

	return addr;
}

void dump_file __(( FILE *_(out) ));
void
dump_file(out)
register FILE	*out;
{
	struct rec_entry	record;
#ifdef NULLCHARS
	register char		c;
	register char		*lp;
#endif
	register char		*buf;
	register int		nlines;

	read_rec(&record);
	nlines = record.r_nlines;
	Nchars = Nlines = 0L;
	if (nlines > 0) do {
		/* now copies directly from the data cache. */
		buf = getblock(getaddr(ptrs_fp) >> 1);
		Nlines++;
#ifndef NULLCHARS
		fputs(buf, out);
		Nchars += 1 + strlen(buf);
#else
		lp = buf;
		while (c = *lp++) {
			if (c == '\n')	/* \n used internally to represent \0 */
				c = '\0';
			putc(c, out);
		}
		Nchars += (lp - buf);	/* includes newline */
#endif
	} while (--nlines && (putc('\n', out), TRUE));

	/* add extra newline if last line was a partial one. */
	(buf[0]) ? putc('\n', out) : --Nchars;

	if (out != stdout)
		fclose(out);
}

/* get a buffer to file "dest", or to stdout if dest == NULL */

void get __(( struct rec_entry **_(src), const char *_(dest) ));
void
get(src, dest)
struct rec_entry	**src;
const char	*dest;
{
	FILE	*outfile;

	if (src == 0)
		return;
	signal(SIGINT, catch_sig);
	if (setjmp(int_env) == 0) {
		if (dest == NULL)
			outfile = stdout;
		else {
#ifdef BACKUPFILES
			if (file_backup(dest) < 0)
				return;
#endif
			if ((outfile = fopen(dest, "w")) == NULL) {
				printf("recover: cannot create %s.\n", dest);
				return;
			}
		}
		seekto((int)(src - buflist));
		if (dest)
			printf("\"%s\"", dest);
		dump_file(outfile);
	} else
		printf("\nAborted!\n");
	if (dest) {
		fclose(outfile);
		printf(" %ld lines, %ld characters.\n", Nlines, Nchars);
	}
	signal(SIGINT, SIG_DFL);
}

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

	for (i = 1; i <= Header.Nbuffers; i++) {
#ifdef BACKUPFILES
		strcpy(tofile, buflist[i]->r_fname);
#else
		strcpy(tofile, buflist[i]->r_fname),
		sprintf(basename(tofile), "#%s", basename(buflist[i]->r_fname));
#endif
tryagain:
		if (tofile[0] == '\0')
#ifdef BACKUPFILES
			strcpy(tofile, buflist[i]->r_bname);
#else
			sprintf(tofile, "#%s", buflist[i]->r_bname);
#endif
		printf("Restoring %s to \"%s\" ", buflist[i]->r_bname, tofile);

		tellme("('?' for options) ", answer);

		switch (answer[0]) {

		case '?':
			printf("y)es, n)o or new file name.\n");
			goto tryagain;

		case 'y':
			if (answer[1] == '\0')
				break;

		case 'n':
			if (answer[1] == '\0')
				continue;

		default:
			strcpy(tofile, answer);
			break;

		case '\0':
			tellme("What file should I use instead? ", tofile);
			goto tryagain;
		}
		get(&buflist[i], tofile);
		nrecovered++;
	}
	printf("Recovered %d buffer%s.\n", nrecovered, nrecovered != 1 ? "s": "");
}

void header __(( void ));
void
header()
{
	printf("Found %d buffer%s last updated: %s",
		Header.Nbuffers,
		Header.Nbuffers != 1 ? "s" : "",
		ctime(&Header.UpdTime));
}

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

void ask_del __(( const char *_(prompt), struct file_pair *_(fp) ));
void
ask_del(prompt, fp)
const char	*prompt;
struct file_pair	*fp;
{
	char	yorn[20];

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

/* [TRH 18-Aug-92] Sanity checks moved to separate routine. */

int checkit __(( struct file_pair *_(fp), int _(ask) ));
int
checkit(fp, ask)
register struct file_pair *fp;
int ask;
{
	ptrs_fp = fopen(fp->file_rec, "rb");
	if (ptrs_fp == NULL) {
		if (Verbose || ask)
			fprintf(stderr, "recover: cannot read rec file (%s).\n",
				fp->file_rec);
		return 0;
	}
	fread((char *) &Header, sizeof Header, 1, ptrs_fp);
	if (Header.Uid != UserID)
		return 0;

#if unix
	/* Don't ask about JOVE's that are still running ... */
#   ifdef LSRHS
	if (pexist(Header.Pid))
		return 0;
#   else
#	ifdef KILL0
	if (kill(Header.Pid, 0) == 0)
		return 0;
#	else
	/* do the next best thing by sending a relatively harmless signal. */
	if (kill(Header.Pid, SIGALRM) == 0)
		return 0;
#	endif
#   endif /* LSRHS */
#endif /* unix */
	if (Header.Nbuffers == 0) {
		if (Verbose || ask)
			fprintf(stderr, "There are no modified buffers in %s; ",
			       fp->file_rec);
		if (ask)
			ask_del("should I delete the tmp file? ", fp);
		return ask;
	}
	if (Header.Nbuffers < 0) {
		if (Verbose || ask)
			fprintf(stderr,
				"recover: %s doesn't look like a jove file.\n",
				fp->file_rec);
		if (ask)
			ask_del("Should I delete it? ", fp);
		return ask;	/* Well, we sort of found something. */
	}

	if (Verbose || ask)
		header();

	if ((data_fd = open(fp->file_data, O_RDONLY)) < 0) {
		if (Verbose || ask)
			fprintf(stderr,
				"recover: I can't read the data file (%s).\n",
				fp->file_data);
		if (ask)
			ask_del("Should I delete the tmp files? ", fp);
		return ask;
	}
	curblock = -1;	/* new datafile -- so reset block pointer(!!) */
	lastblock = lseek(data_fd, 0L, SEEK_END) / BLKSIZ;

	return 1;
}

int doit __(( struct file_pair *_(fp) ));
int
doit(fp)
register struct file_pair	*fp;
{
	char	answer[30];

	if (!checkit(fp, YES))
		return 0;

	makblist();
	list(stdout);

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

		case '\0':
		case 'l':
			header();
			list(stdout);
			break;

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

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

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

			if ((src = getsrc()) == 0)
				break;
			dest = getdest((*src)->r_fname);
			get(src, dest);
			break;
		    }

		case 'r':
			restore();
			break;

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

#ifdef REC_DIR
private char	*Host __(( void ));
private char *
Host()
{
#   ifdef SYSV
#	include <sys/utsname.h>
	static struct utsname	mach ZERO;

	if (mach.sysname[0] == '\0')
		if (uname(&mach) < 0)
			strcpy(mach.sysname, "local");
	return mach.sysname;
#   else /* !SYSV */
#	ifdef BSD4_2
	extern int	gethostname __(( char *_(buf), size_t _(size) ));
	static char	mach[256] ZERO;

	if (mach[0] == '\0')
		if (gethostname(mach, sizeof mach) < 0)
			strcpy(mach, "local");
	return mach;
#	else /* !SYSV && !BSD */
	return "local";
#	endif
#   endif
}

private void MailUser __(( void ));
private void
MailUser()
{
	char mail_cmd[LBSIZE];
	/* Let's be grammatically correct! */
	char *buf_string = (Header.Nbuffers == 1) ? "buffer" : "buffers";
	FILE *mail_pipe;
	struct passwd *pw;
	extern FILE *popen __(( const char *_(command), const char *_(mode) ));

	if ((pw = getpwuid(Header.Uid))== NULL) {
		if (Verbose)
			fprintf(stderr, "jove_recover: couldn't find uid %d.\n",
				Header.Uid);
		return;
	}

	/* Start up mail */
	setuid(getuid());
	sprintf(mail_cmd, "/usr/bin/mail %s", pw->pw_name);
	if ((mail_pipe = popen(mail_cmd + 4, "w")) == NULL &&
	    (mail_pipe = popen(mail_cmd, "w")) == NULL) {
		if (Verbose)
			fprintf(stderr, "jove_recover: couldn't mail %s.\n",
				pw->pw_name);
		return;
	}
#ifdef BSD4_2	/* Do other systems need this too? */
	fprintf(mail_pipe, "To: %s\n", pw->pw_name);
#endif
	fprintf(mail_pipe, "\
Subject: JOVE preserve\n\
 \n\
Jove preserved %d %s when the system \"%s\"\n\
shut down or crashed on %s\n",
		Header.Nbuffers, buf_string, Host(),
		ctime(&Header.UpdTime));
	fprintf(mail_pipe, "\
You can retrieve the %s using Jove's -r\n\
(recover) option, i.e. give the command:\n\
\tjove -r\n\
See the Jove manual for more details\n\n", buf_string);
	list(mail_pipe);
	pclose(mail_pipe);
}

void savetmps __(( int _(notify_user_by_mail) ));
void
savetmps(notify_user_by_mail)
int	notify_user_by_mail;
{
	struct file_pair	*fp;

	if (strcmp(TMP_DIR, REC_DIR) == 0)
		return; 	/* Files are moved to the same place. */
	get_files(TMP_DIR);

	if (fp = First) do {
		int		status;
		struct stat	st;
		char		dest_data[FILESIZE], dest_rec[FILESIZE];
		int		tmp_offset;

		if (stat(fp->file_data, &st) < 0)
			continue;
		if ((ptrs_fp = fopen(fp->file_rec, "rb")) == NULL)
			continue;
		if (fread(&Header, sizeof Header, 1, ptrs_fp) <= 0) {
			fclose(ptrs_fp);
			continue;
		}
		makblist();
		fclose(ptrs_fp);

		if (Verbose)
			fprintf(stderr, "Recovering: %s, %s\n", fp->file_data,
				fp->file_rec);

		/* make sure that file-pair does not exist in destination. */

		make_filename(dest_data, REC_DIR, basename(fp->file_data));
		make_filename(dest_rec, REC_DIR, basename(fp->file_rec));

		tmp_offset = basename(dest_data) - dest_data + l_tempfile;

		while (access(dest_data, F_OK) == 0 ||
		       access(dest_rec, F_OK) == 0) {
			if (dest_rec[tmp_offset] == '9')
				dest_rec[tmp_offset] = 'a' - 1;
			else if (dest_rec[tmp_offset] == 'z')
				++tmp_offset;
			dest_data[tmp_offset] = ++dest_rec[tmp_offset];
		}

		sprintf(blk_buf, "PATH=/bin:/usr/bin;mv %s %s;mv %s %s",
			fp->file_data, dest_data, fp->file_rec, dest_rec);

		if ((status = system(blk_buf)) != 0)
			fprintf(stderr, "recover: non-zero status (%d) returned from mv.\n", status);

		if (chown(dest_data, (int) st.st_uid, (int) st.st_gid) != 0 ||
		    chown(dest_rec, (int) st.st_uid, (int) st.st_gid) != 0) {
			perror("recover: chown failed.");
			continue;
		}
		if (status == 0 && notify_user_by_mail)
			MailUser();
	} while (fp = fp->file_next);
}
#endif /* REC_DIR */

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

	printf("\rChecking %s ...\n", dir);
	get_files(dir);
	if (fp = First) do {
		nfound += doit(fp);
		if (ptrs_fp) {
			fclose(ptrs_fp);
			ptrs_fp = NULL;
		}
		if (data_fd > 0) {
			close(data_fd);
			data_fd = 0;
		}
	} while (fp = fp->file_next);
#ifdef REC_DIR
	free_files();
#endif
	return nfound;
}

int check __(( const char *_(dir) ));
int
check(dir)
const char	*dir;
{
	register struct file_pair	*fp;
	int	nfound = 0;

	get_files(dir);
	if (fp = First) do {
		nfound += checkit(fp, NO);
		if (ptrs_fp) {
			fclose(ptrs_fp);
			ptrs_fp = NULL;
		}
		if (data_fd > 0) {
			close(data_fd);
			data_fd = 0;
		}
	} while (fp = fp->file_next);
#ifdef REC_DIR
	free_files();
#endif
	return nfound;
}

void
main(argc, argv)
int	argc;
char	*argv[];
{
	register int		nfound;
	register char *const	*argvp;
	register const char	*tmp_dir;

	argv[0] = basename(argv[0]);
	l_tempfile = index(p_tempfile, 'X') - p_tempfile;
	UserID = getuid();

 	if (scanvec(argv, "-help")) {
		printf("\
usage: %s [-v]%s [-d directory] [-check]\n\n\
Use \"%s\" after JOVE has died for some\n\
unknown reason.\n",  argv[0], (UserID != 0) ? "" :
#ifdef REC_DIR
		       " [-preserve [-nomail]] [-user name]",
#else
		       " [-user name]",
#endif
		       argv[0]);
		if (UserID == 0) {	/* so only root sees this. */
#ifdef REC_DIR
			printf("\n\
Use \"%s -preserve\" when the system is in the process\n\
of rebooting (i.e., in /etc/rc.)  This moves leftover tmp files\n\
to the directory \"%s\" so that they are not", argv[0], REC_DIR);
			printf("\n\
wiped out by the reboot.  It also notifies the owners of preserved files\n\
by mail, unless \"-nomail\" is given.\n", REC_DIR);
#endif
			printf("\n\
Use \"-user <name>\" to recover the files of a particular user.\n");
		}
		printf("\n\
Use \"%s -d directory\" when the tmp files are stored\n\
in DIRECTORY instead of the default one", argv[0]);
#ifdef REC_DIR
		if (strcmp(TMP_DIR, REC_DIR) != 0)
			printf("s (%s, %s)\n", TMP_DIR, REC_DIR);
		else
#endif
			printf(" (%s)\n", TMP_DIR);
		printf("\n\
Use \"%s -check\" to check for the existence of\n\
preserved JOVE buffers (e.g., in your shell login file.)\n", argv[0]);
		exit(EXIT_SUCCESS);
	}
	if (scanvec(argv, "-v"))
		Verbose++;
#ifdef REC_DIR
	if ((argvp = scanvec(argv, "-preserve")) ||
	    (argvp = scanvec(argv, "-syscrash"))) {
		if (UserID != 0) {
			fprintf(stderr, "%s: must be root for \"%s\".\n",
				argv[0], argvp[0]);
			exit(EXIT_FAILURE);
		}
		if (Verbose)
			printf("Recovering jove files ... ");
		savetmps(scanvec(argv, "-nomail") == NULL);
		if (Verbose)
			printf("Done.\n");
		exit(EXIT_SUCCESS);
	}
#endif
	if (argvp = scanvec(argv, "-uid"))
		UserID = atoi(argvp[1]);
	if (argvp = scanvec(argv, "-user")) {
#ifdef PASSWD
		struct passwd *pw = getpwnam(argvp[1]);

		if (pw)
			UserID = pw->pw_uid;
		else
#endif
			fprintf(stderr, "%s: unknown user (%s).\n",
				argv[0], argvp[1]),
			exit(EXIT_FAILURE);
	}
	if (argvp = scanvec(argv, "-d"))
		tmp_dir = argvp[1];
	else
		tmp_dir = TMP_DIR;
	if (scanvec(argv, "-check")) {
		nfound = check(tmp_dir);
#ifdef REC_DIR
		/* Check whether anything was preserved when system died? */
		if (strcmp(tmp_dir, REC_DIR) != 0)
			nfound += check(REC_DIR);
#endif
		if (nfound != 0)
			printf("\
You have preserved JOVE buffers. (use \"jove -r\" to recover)\n");
		exit(nfound);
	}
	nfound = lookup(tmp_dir);
#ifdef REC_DIR
	/* Check whether anything was preserved when system died? */
	if (strcmp(tmp_dir, REC_DIR) != 0)
		nfound += lookup(REC_DIR);
#endif
	if (nfound == 0)
		printf("There's nothing to recover.\n");

	exit(nfound);
}

/*======================================================================
 * $Log: recover.c,v $
 * Revision 14.31.0.9  1994/01/31  14:31:08  tom
 * (ctime): ANSI-fy prototype.
 *
 * Revision 14.31.0.7  1993/10/29  02:43:04  tom
 * !BACKUPFILES: some fixes in filename generation.
 *
 * Revision 14.31.0.6  1993/10/28  00:16:10  tom
 * (Atari-ST): port to gcc + gnulib.
 *
 * Revision 14.31.0.4  1993/08/02  17:22:53  tom
 * add `-nomail' option, to be used with `-preserve'; doc fix.
 *
 * Revision 14.31.0.1  1993/07/07  13:52:50  tom
 * fix const botches to avoid warnings.
 *
 * Revision 14.31  1993/02/15  02:01:50  tom
 * remove (void) casts.
 *
 * Revision 14.30  1993/02/05  00:07:32  tom
 * cleanup whitespace; some random optimizations; some more Posix-ations.
 *
 * Revision 14.28  1992/10/24  01:24:22  tom
 * convert to "port{ansi,defs}.h" conventions; let mail message make more
 * sense.
 *
 * Revision 14.27  1992/09/21  23:52:32  tom
 * rename -syscrash to -preserve (still support -syscrash); some rephrasing
 * of messages.
 *
 * Revision 14.26  1992/08/26  23:57:06  tom
 * add -check option; avoid overwrites when preserving files in -syscrash;
 * sort temp files by user ID and update time (most recent first);
 * add RCS directives.
 *
 */
