/************************************************************************
 * 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.                                           *
 ************************************************************************/

#include "jove.h"

RCS("$Id: rec.c,v 14.31.0.12 1994/06/23 02:55:32 tom Exp tom $")

#include "io.h"
#define Extern	public
#include "rec.h"

private int		rec_fd ZERO;
private File		*rec_out;
private struct rec_head	Header;

void
recclose()
{
	register File	*fp;

	if ((fp = rec_out) == NULL)
		return;
	unlink(fp->f_name);
	f_close(fp);
#if 0
	rec_out = NULL;
	rec_fd = 0;
#endif
}

/* Goes through all the buffers and syncs them to the disk. */

DEF_INT( "sync-frequency", SyncFreq, V_BASE10 ) = 50;

void
SyncRec()
{
	register Buffer	*b;
	register File	*fp;

	if ((fp = rec_out) == NULL) {		/* Init recover file. */
		char		buf[FILESIZE];
		register char	*fname;

		if (rec_fd != 0)	/* we tried it already, to no avail */
			return;
#ifdef DOS			/* for slow systems, show what's going on */
	    {
		extern int	ILI;
		i_set(ILI, 0);
		swrite("(creating tmp file)", NO);
		flusho();
		updmesg();			/* to restore original mesg */
	    }
#endif
		fname = mktemp(make_filename(buf, TmpFilePath, p_tempfile));

		if ((rec_fd = creat(fname, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) < 0 ||
		    (rec_out = fp = fd_open(fname, F_WRITE|F_LOCKED|F_BINARY,
					    rec_fd, iobuff, BLKSIZ)) == NULL)
			complain("Cannot create \"%s\"; recovery disabled.",
				 fname);

		/* Initialize the record header. */
		Header.Uid = getuid();
		Header.Pid = getpid();
	}
	f_rewind(fp);
	time(&Header.UpdTime);
	Header.Nbuffers = 0;
	if (b = world) do {	/* count buffers, */
		if (b->b_type == B_SCRATCH || !IsModified(b))
			continue;
		Header.Nbuffers++;
	} while (b = b->b_next);
	fputnchar((char *) &Header, sizeof Header, fp);
	if (Header.Nbuffers) {
		struct rec_entry record;
		register Line	*lp;

		lsave();	/* !!! */
		SyncTmp();
		b = world;
		do {
			if (b->b_type == B_SCRATCH || !IsModified(b))
				continue;

			/* dump the buffer info and then the actual line pointers. */
			bzero((char *) &record, sizeof record);
			if (b->b_fname)
				strcpy(record.r_fname, b->b_fname);
			strcpy(record.r_bname, b->b_name);
			record.r_nlines = lineno(b->b_last);
#ifdef FULLRECOVER
			record.r_dot = lineno(b->b_dot);
			record.r_char = b->b_char;
			ino_cpy(record.r_ino, b->b_ino);
			record.r_dev = b->b_dev;
			record.r_mtime = b->b_mtime;
			record.r_statsize = b->b_statsize;
			record.r_type = b->b_type;
			if (b == curbuf)
				record.r_curbuf++;
			record.r_major = b->b_major;
			record.r_minor = b->b_minor;
#endif /* FULLRECOVER */
			fputnchar((char *) &record, sizeof record, fp);

			if (lp = b->b_first) do {
				fputnchar((char *) &lp->l_dline,
					  sizeof lp->l_dline, fp);
			} while (lp = lp->l_next);
		} while (b = b->b_next);
	}
	flush(fp);
}

#ifdef FULLRECOVER		/* the rest of this file */

#include "ctype.h"
#include "process.h"
#include "readdir.h"
#include "temp.h"

extern char	*ctime __(( const time_t *_(time) ));


private int do_fullrecover __(( File *_(rec_inf), int _(data_fd), int _(allp) ));
private int
do_fullrecover(rec_inf, data_fd, allp)
register File	*rec_inf;
int		data_fd;
{
	struct rec_head	header;
	char	iobuff[BLKSIZ];
	int	curblock = -1;
#if !unix
	int	lastblock = lseek(data_fd, 0L, SEEK_END) >> BNO_SHIFT;
#endif
	int	nbuffers = 0;

	f_rewind(rec_inf);

	if (fgetnchar(&header, sizeof header, rec_inf) != NIL) {
		message("[Cannot read header]");
		return EOF;
	}

	while (--header.Nbuffers >= 0) {
		struct rec_entry record;
		disk_line	bad_line;
		register Buffer	*b;

		if (fgetnchar(&record, sizeof record, rec_inf) != NIL) {
			message("[Cannot read buffer record]");
			return EOF;
		}
		if (record.r_nlines == 0)
			continue;

	    {	/* make sure we get a fresh buffer */
		register char	*bname = record.r_bname;
		char		bnamebuf[BNAMESIZE];
		register int	try = 0;

		if (!allp && !yes_or_no_p('Y', "Recover %s \"%s\"? ", bname,
					  record.r_fname[0] ? record.r_fname :
					  "[No file]")) {
			/* skip to next buffer record */
			do {
				disk_line	d;

				if (fgetnchar(&d, sizeof d, rec_inf) != NIL) {
					message("[Cannot skip to next record]");
					return EOF;
				}
			} while (--record.r_nlines);
			continue;
		}

		while ((b = buf_exists(bname)) &&
		       /* b_empty(b) can't be used since assumes b == curbuf */
		       !(lastp(b, b->b_first) && lcontents(b->b_dot)[0] == '\0' &&
			 !b->b_ntbf))
			sprintf(bname = bnamebuf, "%s'%d", record.r_bname, ++try);
		b = do_select((Window *)0, bname);
	    }
		if (record.r_fname[0])
			setfname(b, record.r_fname);

		f_mess("Recovering %b \"%s\"", b, filename(b));

		b->b_modified++;

		/* now, read the lines */
		do {
			disk_line	addr;
			register int	bno;

			if (fgetnchar(&addr, sizeof addr, rec_inf) != NIL) {
				b->b_modified = NO;
				SavLine(b->b_last, "[Unexpected EOF]");
				SetLine(b->b_last);
				message("[Unexpected EOF]");
				return EOF;
			}
			addr >>= 1;
			if ((bno = daddr2bno(addr)) != curblock) {
				register long offset = (long) bno << BNO_SHIFT;
#if !unix
				if ((unsigned) bno >= lastblock)
					addr = 0;
				else
#endif
				if (lseek(data_fd, offset, SEEK_SET) != offset)
					addr = 0;
				else if (read(data_fd, iobuff, BLKSIZ) != BLKSIZ) {
					curblock = -1;
					addr = 0;
				}
				else
					curblock = bno;
			}
			if (addr == 0) {
				if (b->b_modified) {
					b->b_modified = NO;
					bad_line = putline("~~[Bad line]~~");
				}
				b->b_last->l_dline = bad_line;
			}
			else {
				SavLine(b->b_last, iobuff + daddr2off(addr));
			}
			if (--record.r_dot == 0) {
				b->b_dot = b->b_last;
				b->b_char = record.r_char;
			}
		} while (--record.r_nlines &&
			 (b->b_last = listput(b, b->b_last), YES));

		nbuffers++;

		if (!IsModified(b)) {
			b->b_last = listput(b, b->b_last);
			SavLine(b->b_last, "~~[Buffer is corrupted]~~");
			SetLine(b->b_last);
			continue;
		}
		ino_cpy(b->b_ino, record.r_ino);
		b->b_dev = record.r_dev;
		b->b_mtime = record.r_mtime;
		SETBUFTYPE(b, record.r_type);
		b->b_major = record.r_major;
		b->b_minor = record.r_minor;

		if (record.r_curbuf) {
			SetBuf(b);
			tiewind(curwind, b);
		}
	}
	s_mess("Recovered %d buffer%n.", nbuffers);

	return NIL;
}

private int rec_list __(( File *_(rec_inf) ));
private int
rec_list(rec_inf)
register File	*rec_inf;
{
	struct rec_head	header;
	register int	i = 0;
	register char	*timestr;

	f_rewind(rec_inf);

	if (fgetnchar(&header, sizeof header, rec_inf) != NIL) {
		Typeout("[Cannot read header]");
		return EOF;
	}

	timestr = ctime(&header.UpdTime);
	timestr[strlen(timestr) - 1] = '\0';

	Typeout("Found %d buffer%n last updated: %s", header.Nbuffers, timestr);
	Typeout((char *) 0);
	Typeout("NO Lines %-14s File", "Name");
	Typeout("-- ----- %-14s ----", "----");

	while (++i <= header.Nbuffers) {
		struct rec_entry record;

		if (fgetnchar(&record, sizeof record, rec_inf) != NIL) {
			Typeout("[cannot read buffer record]");
			return EOF;
		}
		Typeout("%-2d %-5d %-14s %s", i,
			record.r_nlines,
			record.r_bname,
			record.r_fname[0] ? record.r_fname : "[No file]");
		if (TOabort)
			break;

		/* skip to next record. (lseek should be faster) */
		while (--record.r_nlines >= 0) {
			disk_line	dummy;

			if (fgetnchar(&dummy, sizeof dummy, rec_inf) != NIL) {
				Typeout("[Cannot skip to next record]");
				return EOF;
			}
		}
	}
	return NIL;
}

private void cleanup __(( const char *_(prompt),
			  const char *_(file1), const char *_(file2) ));
private void
cleanup(prompt, file1, file2)
const char	*prompt,
		*file1,
		*file2;
{
	if (yes_or_no_p(NIL, "%s \"%s\", delete? ", prompt, file1)) {
		unlink(file1);
		if (file2)
			unlink(file2);
	}
}

#ifdef REC_DIR
private const char	*RecDir;
#else
#   define RecDir	TmpFilePath
#endif

private int rec_match __(( const char *_(name) ));
private int
rec_match(name)
const char	*name;
{
	extern time_t	time0;
	struct rec_head	header;
	char		rname[FILESIZE],
			dname[FILESIZE];
	register int	i;

#define build_other(fnamebuf, template) do {				\
		register const char	*s = template;			\
		register char		*d = basename(fnamebuf);	\
		while (--i >= 0)					\
			*d++ = *s++;					\
	} while (0)

	strcpy(dname, make_filename(rname, RecDir, name));

	if (d_tempfile[i = numcomp(d_tempfile, name)] == 'X') {

		/* found a datafile: check that we can read the
		   corresponding record file; if not, ask to remove the
		   file if it is ours and it is more than a day old.
		   (this way we minimize the risk of deleting a datafile
		   from under a still-active JOVE) */

		build_other(rname, p_tempfile);

		if (access(rname, R_OK) != 0) {
			struct stat	stbuf;

			if (stat(dname, &stbuf) == 0 &&
			    stbuf.st_uid == getuid() &&
			    stbuf.st_atime < time0 - 24*60*60L) {
				cleanup("Can't find the record file for",
					dname, (char *)0);
			}
		}
		return NO;
	}

	if (p_tempfile[i = numcomp(p_tempfile, name)] == 'X') {

		/* if we found a record file, check that we can read its
		   header; if not, ask to remove it. Then check that it is
		   ours, and that it does not belong to an active JOVE
		   (for instance, ourselves), and that the corresponding
		   datafile exists. */

		register int	fd;

		build_other(dname, d_tempfile);

		if ((fd = open(rname, O_RDONLY)) < 0)
			return NO;

		i = read(fd, &header, sizeof header);
		close(fd);

		if (i != sizeof header || header.Nbuffers < 0) {
			cleanup("Malformed record file", rname, dname);
			return NO;
		}
		if (header.Uid != getuid())
			return NO;
		/* Don't accept if this is our own record file.
		   {check time to avoid stray pid coincidences;
		    only 1 in 30000 chance on most UNIX systems, but
		    mandatory for some others like VMS} */
		if (header.Pid == getpid() && header.UpdTime >= time0)
			return NO;
#if unix
#   ifdef LSRHS
		if (pexist(header.Pid))
			return NO;
#   else
#	ifdef KILL0
		if (kill(header.Pid, 0) == 0)
			return NO;
#	else
		if (kill(header.Pid, SIGALRM) == 0)
			return NO;
#	endif
#   endif
#endif
		if (access(dname, R_OK) != 0) {
			cleanup("Can't find the datafile for", rname, (char *)0);
			return NO;
		}
		if (header.Nbuffers == 0) {
			cleanup("No modified buffers in", rname, dname);
			return NO;
		}
		return YES;
	}
	return NO;
}

private int do_recdir __(( const char *_(rec_dir) ));
private int
do_recdir(rec_dir)
const char	*rec_dir;
{
	char	**dir_vec;
	int	nentries;
	int	ent;

#ifdef REC_DIR
	RecDir = rec_dir;
#endif
#ifdef CHDIR
#   ifndef TINY
	ask_dir_only--;
#   endif
#endif
	if ((nentries = scandir(rec_dir, &dir_vec, rec_match, (int (*)()) 0)) < 0) {
		message(IOerr("scan directory", rec_dir));
		return 0;
	}
	for (ent = 0; ent < nentries; ent++) {
		char	rname[FILESIZE],
			dname[FILESIZE];
		File	*rec_inf;
		int	data_fd;
		int	i = numcomp(p_tempfile, dir_vec[ent]);

		strcpy(dname, make_filename(rname, rec_dir, dir_vec[ent]));
		build_other(dname, d_tempfile);

		if (rec_inf = open_file(rname, iobuff, F_READ|F_BINARY)) {
			if ((data_fd = open(dname, O_RDONLY)) < 0) {
				message(IOerr("read datafile", dname));
			}
			else {
				TOstart("*Recover*", TRUE);
				rec_list(rec_inf);
				Typeout((char *)0);
				Typeout("[recover N)one, S)ome, A)ll]");
				TOstop();
				message("[recover N)one, S)ome, A)ll]");
				i = toupper(getch());
				if (i != 'N' && i != AbortChar)
					do_fullrecover(rec_inf, data_fd, i-'S');
				close(data_fd);
			}
			f_close(rec_inf);
		}
		cleanup(strcpy(genbuf, mesgbuf), rname, dname);
	}
	freedir(&dir_vec, nentries);

	return nentries;
}

DEF_CMD( "recover", FullRecover, NO ) _IF(def FULLRECOVER)
{
	char		filebuf[FILESIZE];
	const char	*tmp_dir = TmpFilePath;
	int		nfound;

	if (exp_p) {
#ifdef CHDIR
#   ifndef TINY
		ask_dir_only = YES;
#   endif
#endif
		tmp_dir = ask_file((char *) 0, tmp_dir, filebuf);
	}
	f_mess(ProcArgFmt, tmp_dir);
	nfound = do_recdir(tmp_dir);
#ifdef REC_DIR
	if (strcmp(tmp_dir, REC_DIR) != 0) {
		f_mess("%N: %f %s, %s", tmp_dir, REC_DIR);
		nfound += do_recdir(REC_DIR);
	}
#endif
	if (nfound == 0)
		add_mess(" (Nothing to recover.)");
}

#endif /* FULLRECOVER */

/*======================================================================
 * $Log: rec.c,v $
 * Revision 14.31.0.12  1994/06/23  02:55:32  tom
 * (do_recdir): fix name of scratch buffer;
 * (do_fullrecover): fix uniquized buffer names.
 *
 * Revision 14.31  1993/02/17  01:25:25  tom
 * remove (void) casts; lotsa random optimizations.
 *
 * Revision 14.30  1993/02/06  00:48:32  tom
 * cleanup whitespace; some random optimizations; some more Posix-ation.
 *
 * Revision 14.27  1992/09/22  02:03:53  tom
 * add statsize field to header record.
 *
 * Revision 14.26  1992/08/26  23:56:58  tom
 * add RCS directives.
 *
 */
