119304Speter/*-
219304Speter * Copyright (c) 1993, 1994
319304Speter *	The Regents of the University of California.  All rights reserved.
419304Speter * Copyright (c) 1993, 1994, 1995, 1996
519304Speter *	Keith Bostic.  All rights reserved.
619304Speter *
719304Speter * See the LICENSE file for redistribution information.
819304Speter */
919304Speter
1019304Speter#include "config.h"
1119304Speter
1219304Speter#ifndef lint
13254225Speterstatic const char sccsid[] = "$Id: recover.c,v 11.2 2012/10/09 08:06:58 zy Exp $";
1419304Speter#endif /* not lint */
1519304Speter
16254225Speter#include <sys/types.h>
1719304Speter#include <sys/queue.h>
1819304Speter#include <sys/stat.h>
1919304Speter
2019304Speter/*
2119304Speter * We include <sys/file.h>, because the open #defines were found there
2219304Speter * on historical systems.  We also include <fcntl.h> because the open(2)
2319304Speter * #defines are found there on newer systems.
2419304Speter */
2519304Speter#include <sys/file.h>
2619304Speter
2719304Speter#include <bitstring.h>
2819304Speter#include <dirent.h>
2919304Speter#include <errno.h>
3019304Speter#include <fcntl.h>
3119304Speter#include <limits.h>
3219304Speter#include <pwd.h>
33254225Speter#include <netinet/in.h>		/* Required by resolv.h. */
34254225Speter#include <resolv.h>
3519304Speter#include <stdio.h>
3619304Speter#include <stdlib.h>
3719304Speter#include <string.h>
3819304Speter#include <time.h>
3919304Speter#include <unistd.h>
4019304Speter
41254225Speter#include "../ex/version.h"
4219304Speter#include "common.h"
4319304Speter#include "pathnames.h"
4419304Speter
4519304Speter/*
4619304Speter * Recovery code.
4719304Speter *
4819304Speter * The basic scheme is as follows.  In the EXF structure, we maintain full
4919304Speter * paths of a b+tree file and a mail recovery file.  The former is the file
5019304Speter * used as backing store by the DB package.  The latter is the file that
5119304Speter * contains an email message to be sent to the user if we crash.  The two
5219304Speter * simple states of recovery are:
5319304Speter *
5419304Speter *	+ first starting the edit session:
5519304Speter *		the b+tree file exists and is mode 700, the mail recovery
5619304Speter *		file doesn't exist.
5719304Speter *	+ after the file has been modified:
5819304Speter *		the b+tree file exists and is mode 600, the mail recovery
5919304Speter *		file exists, and is exclusively locked.
6019304Speter *
6119304Speter * In the EXF structure we maintain a file descriptor that is the locked
62254225Speter * file descriptor for the mail recovery file.
6319304Speter *
6419304Speter * To find out if a recovery file/backing file pair are in use, try to get
6519304Speter * a lock on the recovery file.
6619304Speter *
6719304Speter * To find out if a backing file can be deleted at boot time, check for an
6819304Speter * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
6919304Speter * special stuff into the backing file itself, or correlate the files at
7019304Speter * boot time, neither of which looks like fun.)  Note also that there's a
7119304Speter * window between when the file is created and the X bit is set.  It's small,
7219304Speter * but it's there.  To fix the window, check for 0 length files as well.
7319304Speter *
7419304Speter * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
7519304Speter * this DOES NOT mean that any initialization has been done, only that we
7619304Speter * haven't yet failed at setting up or doing recovery.
7719304Speter *
7819304Speter * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
7919304Speter * If that bit is not set when ending a file session:
8019304Speter *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
8119304Speter *	they are unlink(2)'d, and free(3)'d.
8219304Speter *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
8319304Speter *
8419304Speter * The backing b+tree file is set up when a file is first edited, so that
8519304Speter * the DB package can use it for on-disk caching and/or to snapshot the
8619304Speter * file.  When the file is first modified, the mail recovery file is created,
8719304Speter * the backing file permissions are updated, the file is sync(2)'d to disk,
8819304Speter * and the timer is started.  Then, at RCV_PERIOD second intervals, the
8919304Speter * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
9019304Speter * means that the data structures (SCR, EXF, the underlying tree structures)
9119304Speter * must be consistent when the signal arrives.
9219304Speter *
93254225Speter * The recovery mail file contains normal mail headers, with two additional
9419304Speter *
95254225Speter *	X-vi-data: <file|path>;<base64 encoded path>
9619304Speter *
97254225Speter * MIME headers; the folding character is limited to ' '.
9819304Speter *
99254225Speter * Btree files are named "vi.XXXXXX" and recovery files are named
100254225Speter * "recover.XXXXXX".
10119304Speter */
10219304Speter
103254225Speter#define	VI_DHEADER	"X-vi-data:"
10419304Speter
10519304Speterstatic int	 rcv_copy __P((SCR *, int, char *));
10619304Speterstatic void	 rcv_email __P((SCR *, char *));
10719304Speterstatic int	 rcv_mailfile __P((SCR *, int, char *));
108254225Speterstatic int	 rcv_mktemp __P((SCR *, char *, char *));
109254225Speterstatic int	 rcv_dlnwrite __P((SCR *, const char *, const char *, FILE *));
110254225Speterstatic int	 rcv_dlnread __P((SCR *, char **, char **, FILE *));
11119304Speter
11219304Speter/*
11319304Speter * rcv_tmp --
11419304Speter *	Build a file name that will be used as the recovery file.
11519304Speter *
11619304Speter * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
11719304Speter */
11819304Speterint
119254225Speterrcv_tmp(
120254225Speter	SCR *sp,
121254225Speter	EXF *ep,
122254225Speter	char *name)
12319304Speter{
12419304Speter	struct stat sb;
12519304Speter	int fd;
126254225Speter	char *dp, *path;
12719304Speter
12819304Speter	/*
12919304Speter	 * !!!
13019304Speter	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
13119304Speter	 *
13219304Speter	 *
13319304Speter	 * If the recovery directory doesn't exist, try and create it.  As
13419304Speter	 * the recovery files are themselves protected from reading/writing
13519304Speter	 * by other than the owner, the worst that can happen is that a user
13619304Speter	 * would have permission to remove other user's recovery files.  If
13719304Speter	 * the sticky bit has the BSD semantics, that too will be impossible.
13819304Speter	 */
13919304Speter	if (opts_empty(sp, O_RECDIR, 0))
14019304Speter		goto err;
14119304Speter	dp = O_STR(sp, O_RECDIR);
14219304Speter	if (stat(dp, &sb)) {
14319304Speter		if (errno != ENOENT || mkdir(dp, 0)) {
14419304Speter			msgq(sp, M_SYSERR, "%s", dp);
14519304Speter			goto err;
14619304Speter		}
14719304Speter		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
14819304Speter	}
14919304Speter
150254225Speter	if ((path = join(dp, "vi.XXXXXX")) == NULL)
15119304Speter		goto err;
152254225Speter	if ((fd = rcv_mktemp(sp, path, dp)) == -1) {
153254225Speter		free(path);
154254225Speter		goto err;
155254225Speter	}
156254225Speter	(void)fchmod(fd, S_IRWXU);
15719304Speter	(void)close(fd);
15819304Speter
159254225Speter	ep->rcv_path = path;
160254225Speter	if (0) {
16119304Spetererr:		msgq(sp, M_ERR,
16219304Speter		    "056|Modifications not recoverable if the session fails");
16319304Speter		return (1);
16419304Speter	}
16519304Speter
16619304Speter	/* We believe the file is recoverable. */
16719304Speter	F_SET(ep, F_RCV_ON);
16819304Speter	return (0);
16919304Speter}
17019304Speter
17119304Speter/*
17219304Speter * rcv_init --
17319304Speter *	Force the file to be snapshotted for recovery.
17419304Speter *
17519304Speter * PUBLIC: int rcv_init __P((SCR *));
17619304Speter */
17719304Speterint
178254225Speterrcv_init(SCR *sp)
17919304Speter{
18019304Speter	EXF *ep;
18119304Speter	recno_t lno;
18219304Speter
18319304Speter	ep = sp->ep;
18419304Speter
18519304Speter	/* Only do this once. */
18619304Speter	F_CLR(ep, F_FIRSTMODIFY);
18719304Speter
18819304Speter	/* If we already know the file isn't recoverable, we're done. */
18919304Speter	if (!F_ISSET(ep, F_RCV_ON))
19019304Speter		return (0);
19119304Speter
19219304Speter	/* Turn off recoverability until we figure out if this will work. */
19319304Speter	F_CLR(ep, F_RCV_ON);
19419304Speter
19519304Speter	/* Test if we're recovering a file, not editing one. */
19619304Speter	if (ep->rcv_mpath == NULL) {
19719304Speter		/* Build a file to mail to the user. */
19819304Speter		if (rcv_mailfile(sp, 0, NULL))
19919304Speter			goto err;
20019304Speter
20119304Speter		/* Force a read of the entire file. */
20219304Speter		if (db_last(sp, &lno))
20319304Speter			goto err;
20419304Speter
20519304Speter		/* Turn on a busy message, and sync it to backing store. */
20619304Speter		sp->gp->scr_busy(sp,
20719304Speter		    "057|Copying file for recovery...", BUSY_ON);
20819304Speter		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
20919304Speter			msgq_str(sp, M_SYSERR, ep->rcv_path,
21019304Speter			    "058|Preservation failed: %s");
21119304Speter			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
21219304Speter			goto err;
21319304Speter		}
21419304Speter		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
21519304Speter	}
21619304Speter
21719304Speter	/* Turn off the owner execute bit. */
21819304Speter	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
21919304Speter
22019304Speter	/* We believe the file is recoverable. */
22119304Speter	F_SET(ep, F_RCV_ON);
22219304Speter	return (0);
22319304Speter
22419304Spetererr:	msgq(sp, M_ERR,
22519304Speter	    "059|Modifications not recoverable if the session fails");
22619304Speter	return (1);
22719304Speter}
22819304Speter
22919304Speter/*
23019304Speter * rcv_sync --
23119304Speter *	Sync the file, optionally:
23219304Speter *		flagging the backup file to be preserved
23319304Speter *		snapshotting the backup file and send email to the user
23419304Speter *		sending email to the user if the file was modified
23519304Speter *		ending the file session
23619304Speter *
23719304Speter * PUBLIC: int rcv_sync __P((SCR *, u_int));
23819304Speter */
23919304Speterint
240254225Speterrcv_sync(
241254225Speter	SCR *sp,
242254225Speter	u_int flags)
24319304Speter{
24419304Speter	EXF *ep;
24519304Speter	int fd, rval;
246254225Speter	char *dp, *buf;
24719304Speter
24819304Speter	/* Make sure that there's something to recover/sync. */
24919304Speter	ep = sp->ep;
25019304Speter	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
25119304Speter		return (0);
25219304Speter
25319304Speter	/* Sync the file if it's been modified. */
25419304Speter	if (F_ISSET(ep, F_MODIFIED)) {
25519304Speter		SIGBLOCK;
25619304Speter		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
25719304Speter			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
25819304Speter			msgq_str(sp, M_SYSERR,
25919304Speter			    ep->rcv_path, "060|File backup failed: %s");
26019304Speter			SIGUNBLOCK;
26119304Speter			return (1);
26219304Speter		}
26319304Speter		SIGUNBLOCK;
26419304Speter
26519304Speter		/* REQUEST: don't remove backing file on exit. */
26619304Speter		if (LF_ISSET(RCV_PRESERVE))
26719304Speter			F_SET(ep, F_RCV_NORM);
26819304Speter
26919304Speter		/* REQUEST: send email. */
27019304Speter		if (LF_ISSET(RCV_EMAIL))
27119304Speter			rcv_email(sp, ep->rcv_mpath);
27219304Speter	}
27319304Speter
27419304Speter	/*
27519304Speter	 * !!!
27619304Speter	 * Each time the user exec's :preserve, we have to snapshot all of
27719304Speter	 * the recovery information, i.e. it's like the user re-edited the
27819304Speter	 * file.  We copy the DB(3) backing file, and then create a new mail
27919304Speter	 * recovery file, it's simpler than exiting and reopening all of the
28019304Speter	 * underlying files.
28119304Speter	 *
28219304Speter	 * REQUEST: snapshot the file.
28319304Speter	 */
28419304Speter	rval = 0;
28519304Speter	if (LF_ISSET(RCV_SNAPSHOT)) {
28619304Speter		if (opts_empty(sp, O_RECDIR, 0))
28719304Speter			goto err;
28819304Speter		dp = O_STR(sp, O_RECDIR);
289254225Speter		if ((buf = join(dp, "vi.XXXXXX")) == NULL) {
290254225Speter			msgq(sp, M_SYSERR, NULL);
29119304Speter			goto err;
292254225Speter		}
293254225Speter		if ((fd = rcv_mktemp(sp, buf, dp)) == -1) {
294254225Speter			free(buf);
295254225Speter			goto err;
296254225Speter		}
29719304Speter		sp->gp->scr_busy(sp,
29819304Speter		    "061|Copying file for recovery...", BUSY_ON);
29919304Speter		if (rcv_copy(sp, fd, ep->rcv_path) ||
30019304Speter		    close(fd) || rcv_mailfile(sp, 1, buf)) {
30119304Speter			(void)unlink(buf);
30219304Speter			(void)close(fd);
30319304Speter			rval = 1;
30419304Speter		}
305254225Speter		free(buf);
30619304Speter		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
30719304Speter	}
30819304Speter	if (0) {
30919304Spetererr:		rval = 1;
31019304Speter	}
31119304Speter
31219304Speter	/* REQUEST: end the file session. */
31319304Speter	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
31419304Speter		rval = 1;
31519304Speter
31619304Speter	return (rval);
31719304Speter}
31819304Speter
31919304Speter/*
32019304Speter * rcv_mailfile --
32119304Speter *	Build the file to mail to the user.
32219304Speter */
32319304Speterstatic int
324254225Speterrcv_mailfile(
325254225Speter	SCR *sp,
326254225Speter	int issync,
327254225Speter	char *cp_path)
32819304Speter{
32919304Speter	EXF *ep;
33019304Speter	GS *gp;
33119304Speter	struct passwd *pw;
332254225Speter	int len;
33319304Speter	time_t now;
33419304Speter	uid_t uid;
33519304Speter	int fd;
336254225Speter	FILE *fp;
337254225Speter	char *dp, *p, *t, *qt, *buf, *mpath;
33819304Speter	char *t1, *t2, *t3;
339254225Speter	int st;
34019304Speter
34119304Speter	/*
34219304Speter	 * XXX
343254225Speter	 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3)
344254225Speter	 * first, then fallback to _POSIX_HOST_NAME_MAX.
34519304Speter	 */
346254225Speter	char *host;
347254225Speter	long hostmax = sysconf(_SC_HOST_NAME_MAX);
348254225Speter	if (hostmax < 0)
349254225Speter		hostmax = _POSIX_HOST_NAME_MAX;
35019304Speter
35119304Speter	gp = sp->gp;
35219304Speter	if ((pw = getpwuid(uid = getuid())) == NULL) {
35319304Speter		msgq(sp, M_ERR,
35419304Speter		    "062|Information on user id %u not found", uid);
35519304Speter		return (1);
35619304Speter	}
35719304Speter
35819304Speter	if (opts_empty(sp, O_RECDIR, 0))
35919304Speter		return (1);
36019304Speter	dp = O_STR(sp, O_RECDIR);
361254225Speter	if ((mpath = join(dp, "recover.XXXXXX")) == NULL) {
362254225Speter		msgq(sp, M_SYSERR, NULL);
36319304Speter		return (1);
364254225Speter	}
365254225Speter	if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) {
366254225Speter		free(mpath);
367254225Speter		return (1);
368254225Speter	}
369254225Speter	if ((fp = fdopen(fd, "w")) == NULL) {
370254225Speter		free(mpath);
371254225Speter		close(fd);
372254225Speter		return (1);
373254225Speter	}
37419304Speter
37519304Speter	/*
37619304Speter	 * XXX
37719304Speter	 * We keep an open lock on the file so that the recover option can
37819304Speter	 * distinguish between files that are live and those that need to
37919304Speter	 * be recovered.  There's an obvious window between the mkstemp call
38019304Speter	 * and the lock, but it's pretty small.
38119304Speter	 */
38219304Speter	ep = sp->ep;
383254225Speter	if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS)
38419304Speter		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
38519304Speter	if (!issync) {
38619304Speter		/* Save the recover file descriptor, and mail path. */
387254225Speter		ep->rcv_fd = dup(fd);
388254225Speter		ep->rcv_mpath = mpath;
38919304Speter		cp_path = ep->rcv_path;
39019304Speter	}
39119304Speter
39219304Speter	t = sp->frp->name;
39319304Speter	if ((p = strrchr(t, '/')) == NULL)
39419304Speter		p = t;
39519304Speter	else
39619304Speter		++p;
39719304Speter	(void)time(&now);
398254225Speter
399254225Speter	if ((st = rcv_dlnwrite(sp, "file", t, fp))) {
400254225Speter		if (st == 1)
401254225Speter			goto werr;
402254225Speter		goto err;
403254225Speter	}
404254225Speter	if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) {
405254225Speter		if (st == 1)
406254225Speter			goto werr;
407254225Speter		goto err;
408254225Speter	}
409254225Speter
410254225Speter	MALLOC(sp, host, char *, hostmax + 1);
411254225Speter	if (host == NULL)
412254225Speter		goto err;
413254225Speter	(void)gethostname(host, hostmax + 1);
414254225Speter
415254225Speter	len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n",
416254225Speter	    "From: root@", host, " (Nvi recovery program)",
417254225Speter	    "To: ", pw->pw_name, "@", host,
41819304Speter	    "Subject: Nvi saved the file ", p,
41919304Speter	    "Precedence: bulk");		/* For vacation(1). */
420254225Speter	if (len < 0) {
421254225Speter		free(host);
42219304Speter		goto werr;
423254225Speter	}
42419304Speter
425254225Speter	if ((qt = quote(t)) == NULL) {
426254225Speter		free(host);
427254225Speter		msgq(sp, M_SYSERR, NULL);
428254225Speter		goto err;
429254225Speter	}
430254225Speter	len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
43119304Speter	    "On ", ctime(&now), ", the user ", pw->pw_name,
43219304Speter	    " was editing a file named ", t, " on the machine ",
43319304Speter	    host, ", when it was saved for recovery. ",
43419304Speter	    "You can recover most, if not all, of the changes ",
43519304Speter	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
436254225Speter	    gp->progname, " -r ", qt);
437254225Speter	free(qt);
438254225Speter	free(host);
439254225Speter	if (buf == NULL) {
440254225Speter		msgq(sp, M_SYSERR, NULL);
44119304Speter		goto err;
44219304Speter	}
44319304Speter
44419304Speter	/*
44519304Speter	 * Format the message.  (Yes, I know it's silly.)
44619304Speter	 * Requires that the message end in a <newline>.
44719304Speter	 */
44819304Speter#define	FMTCOLS	60
44919304Speter	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
45019304Speter		/* Check for a short length. */
45119304Speter		if (len <= FMTCOLS) {
45219304Speter			t2 = t1 + (len - 1);
45319304Speter			goto wout;
45419304Speter		}
45519304Speter
45619304Speter		/* Check for a required <newline>. */
45719304Speter		t2 = strchr(t1, '\n');
45819304Speter		if (t2 - t1 <= FMTCOLS)
45919304Speter			goto wout;
46019304Speter
46119304Speter		/* Find the closest space, if any. */
46219304Speter		for (t3 = t2; t2 > t1; --t2)
46319304Speter			if (*t2 == ' ') {
46419304Speter				if (t2 - t1 <= FMTCOLS)
46519304Speter					goto wout;
46619304Speter				t3 = t2;
46719304Speter			}
46819304Speter		t2 = t3;
46919304Speter
47019304Speter		/* t2 points to the last character to display. */
47119304Speterwout:		*t2++ = '\n';
47219304Speter
47319304Speter		/* t2 points one after the last character to display. */
474254225Speter		if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) {
475254225Speter			free(buf);
47619304Speter			goto werr;
477254225Speter		}
47819304Speter	}
47919304Speter
48019304Speter	if (issync) {
481254225Speter		fflush(fp);
48219304Speter		rcv_email(sp, mpath);
483254225Speter		free(mpath);
48419304Speter	}
485254225Speter	if (fclose(fp)) {
486254225Speter		free(buf);
487254225Speterwerr:		msgq(sp, M_SYSERR, "065|Recovery file");
488254225Speter		goto err;
489254225Speter	}
490254225Speter	free(buf);
49119304Speter	return (0);
49219304Speter
49319304Spetererr:	if (!issync)
49419304Speter		ep->rcv_fd = -1;
495254225Speter	if (fp != NULL)
496254225Speter		(void)fclose(fp);
49719304Speter	return (1);
49819304Speter}
49919304Speter
50019304Speter/*
50119304Speter *	people making love
50219304Speter *	never exactly the same
50319304Speter *	just like a snowflake
50419304Speter *
50519304Speter * rcv_list --
50619304Speter *	List the files that can be recovered by this user.
50719304Speter *
50819304Speter * PUBLIC: int rcv_list __P((SCR *));
50919304Speter */
51019304Speterint
511254225Speterrcv_list(SCR *sp)
51219304Speter{
51319304Speter	struct dirent *dp;
51419304Speter	struct stat sb;
51519304Speter	DIR *dirp;
51619304Speter	FILE *fp;
51719304Speter	int found;
518254225Speter	char *p, *file, *path;
519254225Speter	char *dtype, *data;
520254225Speter	int st;
52119304Speter
52219304Speter	/* Open the recovery directory for reading. */
52319304Speter	if (opts_empty(sp, O_RECDIR, 0))
52419304Speter		return (1);
52519304Speter	p = O_STR(sp, O_RECDIR);
52619304Speter	if (chdir(p) || (dirp = opendir(".")) == NULL) {
52719304Speter		msgq_str(sp, M_SYSERR, p, "recdir: %s");
52819304Speter		return (1);
52919304Speter	}
53019304Speter
53119304Speter	/* Read the directory. */
53219304Speter	for (found = 0; (dp = readdir(dirp)) != NULL;) {
53319304Speter		if (strncmp(dp->d_name, "recover.", 8))
53419304Speter			continue;
53519304Speter
536254225Speter		/* If it's readable, it's recoverable. */
537254225Speter		if ((fp = fopen(dp->d_name, "r")) == NULL)
53819304Speter			continue;
53919304Speter
540254225Speter		switch (file_lock(sp, NULL, fileno(fp), 1)) {
54119304Speter		case LOCK_FAILED:
54219304Speter			/*
54319304Speter			 * XXX
54419304Speter			 * Assume that a lock can't be acquired, but that we
54519304Speter			 * should permit recovery anyway.  If this is wrong,
54619304Speter			 * and someone else is using the file, we're going to
54719304Speter			 * die horribly.
54819304Speter			 */
54919304Speter			break;
55019304Speter		case LOCK_SUCCESS:
55119304Speter			break;
55219304Speter		case LOCK_UNAVAIL:
55319304Speter			/* If it's locked, it's live. */
55419304Speter			(void)fclose(fp);
55519304Speter			continue;
55619304Speter		}
55719304Speter
55819304Speter		/* Check the headers. */
559254225Speter		for (file = NULL, path = NULL;
560254225Speter		    file == NULL || path == NULL;) {
561254225Speter			if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
562254225Speter				if (st == 1)
563254225Speter					msgq_str(sp, M_ERR, dp->d_name,
564254225Speter					    "066|%s: malformed recovery file");
565254225Speter				goto next;
566254225Speter			}
567254225Speter			if (dtype == NULL)
568254225Speter				continue;
569254225Speter			if (!strcmp(dtype, "file"))
570254225Speter				file = data;
571254225Speter			else if (!strcmp(dtype, "path"))
572254225Speter				path = data;
573254225Speter			else
574254225Speter				free(data);
57519304Speter		}
57619304Speter
57719304Speter		/*
57819304Speter		 * If the file doesn't exist, it's an orphaned recovery file,
57919304Speter		 * toss it.
58019304Speter		 *
58119304Speter		 * XXX
58219304Speter		 * This can occur if the backup file was deleted and we crashed
58319304Speter		 * before deleting the email file.
58419304Speter		 */
58519304Speter		errno = 0;
586254225Speter		if (stat(path, &sb) &&
58719304Speter		    errno == ENOENT) {
58819304Speter			(void)unlink(dp->d_name);
58919304Speter			goto next;
59019304Speter		}
59119304Speter
59219304Speter		/* Get the last modification time and display. */
59319304Speter		(void)fstat(fileno(fp), &sb);
59419304Speter		(void)printf("%.24s: %s\n",
595254225Speter		    ctime(&sb.st_mtime), file);
59619304Speter		found = 1;
59719304Speter
59819304Speter		/* Close, discarding lock. */
59919304Speternext:		(void)fclose(fp);
600254225Speter		if (file != NULL)
601254225Speter			free(file);
602254225Speter		if (path != NULL)
603254225Speter			free(path);
60419304Speter	}
60519304Speter	if (found == 0)
606254225Speter		(void)printf("%s: No files to recover\n", sp->gp->progname);
60719304Speter	(void)closedir(dirp);
60819304Speter	return (0);
60919304Speter}
61019304Speter
61119304Speter/*
61219304Speter * rcv_read --
61319304Speter *	Start a recovered file as the file to edit.
61419304Speter *
61519304Speter * PUBLIC: int rcv_read __P((SCR *, FREF *));
61619304Speter */
61719304Speterint
618254225Speterrcv_read(
619254225Speter	SCR *sp,
620254225Speter	FREF *frp)
62119304Speter{
62219304Speter	struct dirent *dp;
62319304Speter	struct stat sb;
62419304Speter	DIR *dirp;
625254225Speter	FILE *fp;
62619304Speter	EXF *ep;
627254225Speter	struct timespec rec_mtim = { 0, 0 };
628254225Speter	int found, locked = 0, requested, sv_fd;
62919304Speter	char *name, *p, *t, *rp, *recp, *pathp;
630254225Speter	char *file, *path, *recpath;
631254225Speter	char *dtype, *data;
632254225Speter	int st;
63319304Speter
63419304Speter	if (opts_empty(sp, O_RECDIR, 0))
63519304Speter		return (1);
63619304Speter	rp = O_STR(sp, O_RECDIR);
63719304Speter	if ((dirp = opendir(rp)) == NULL) {
63819304Speter		msgq_str(sp, M_ERR, rp, "%s");
63919304Speter		return (1);
64019304Speter	}
64119304Speter
64219304Speter	name = frp->name;
64319304Speter	sv_fd = -1;
64419304Speter	recp = pathp = NULL;
64519304Speter	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
64619304Speter		if (strncmp(dp->d_name, "recover.", 8))
64719304Speter			continue;
648254225Speter		if ((recpath = join(rp, dp->d_name)) == NULL) {
649254225Speter			msgq(sp, M_SYSERR, NULL);
650254225Speter			continue;
651254225Speter		}
65219304Speter
653254225Speter		/* If it's readable, it's recoverable. */
654254225Speter		if ((fp = fopen(recpath, "r")) == NULL) {
655254225Speter			free(recpath);
65619304Speter			continue;
657254225Speter		}
65819304Speter
659254225Speter		switch (file_lock(sp, NULL, fileno(fp), 1)) {
66019304Speter		case LOCK_FAILED:
66119304Speter			/*
66219304Speter			 * XXX
66319304Speter			 * Assume that a lock can't be acquired, but that we
66419304Speter			 * should permit recovery anyway.  If this is wrong,
66519304Speter			 * and someone else is using the file, we're going to
66619304Speter			 * die horribly.
66719304Speter			 */
66819304Speter			locked = 0;
66919304Speter			break;
67019304Speter		case LOCK_SUCCESS:
67119304Speter			locked = 1;
67219304Speter			break;
67319304Speter		case LOCK_UNAVAIL:
67419304Speter			/* If it's locked, it's live. */
675254225Speter			(void)fclose(fp);
67619304Speter			continue;
67719304Speter		}
67819304Speter
67919304Speter		/* Check the headers. */
680254225Speter		for (file = NULL, path = NULL;
681254225Speter		    file == NULL || path == NULL;) {
682254225Speter			if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
683254225Speter				if (st == 1)
684254225Speter					msgq_str(sp, M_ERR, dp->d_name,
685254225Speter					    "067|%s: malformed recovery file");
686254225Speter				goto next;
687254225Speter			}
688254225Speter			if (dtype == NULL)
689254225Speter				continue;
690254225Speter			if (!strcmp(dtype, "file"))
691254225Speter				file = data;
692254225Speter			else if (!strcmp(dtype, "path"))
693254225Speter				path = data;
694254225Speter			else
695254225Speter				free(data);
69619304Speter		}
69719304Speter		++found;
69819304Speter
69919304Speter		/*
70019304Speter		 * If the file doesn't exist, it's an orphaned recovery file,
70119304Speter		 * toss it.
70219304Speter		 *
70319304Speter		 * XXX
70419304Speter		 * This can occur if the backup file was deleted and we crashed
70519304Speter		 * before deleting the email file.
70619304Speter		 */
70719304Speter		errno = 0;
708254225Speter		if (stat(path, &sb) &&
70919304Speter		    errno == ENOENT) {
71019304Speter			(void)unlink(dp->d_name);
71119304Speter			goto next;
71219304Speter		}
71319304Speter
71419304Speter		/* Check the file name. */
715254225Speter		if (strcmp(file, name))
71619304Speter			goto next;
71719304Speter
71819304Speter		++requested;
71919304Speter
720254225Speter		/* If we've found more than one, take the most recent. */
721254225Speter		(void)fstat(fileno(fp), &sb);
722254225Speter		if (recp == NULL ||
723254225Speter		    timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) {
72419304Speter			p = recp;
72519304Speter			t = pathp;
726254225Speter			recp = recpath;
727254225Speter			pathp = path;
72819304Speter			if (p != NULL) {
72919304Speter				free(p);
73019304Speter				free(t);
73119304Speter			}
732254225Speter			rec_mtim = sb.st_mtimespec;
73319304Speter			if (sv_fd != -1)
73419304Speter				(void)close(sv_fd);
735254225Speter			sv_fd = dup(fileno(fp));
736254225Speter		} else {
737254225Speternext:			free(recpath);
738254225Speter			if (path != NULL)
739254225Speter				free(path);
740254225Speter		}
741254225Speter		(void)fclose(fp);
742254225Speter		if (file != NULL)
743254225Speter			free(file);
74419304Speter	}
74519304Speter	(void)closedir(dirp);
74619304Speter
74719304Speter	if (recp == NULL) {
74819304Speter		msgq_str(sp, M_INFO, name,
74919304Speter		    "068|No files named %s, readable by you, to recover");
75019304Speter		return (1);
75119304Speter	}
75219304Speter	if (found) {
75319304Speter		if (requested > 1)
75419304Speter			msgq(sp, M_INFO,
75519304Speter	    "069|There are older versions of this file for you to recover");
75619304Speter		if (found > requested)
75719304Speter			msgq(sp, M_INFO,
75819304Speter			    "070|There are other files for you to recover");
75919304Speter	}
76019304Speter
76119304Speter	/*
76219304Speter	 * Create the FREF structure, start the btree file.
76319304Speter	 *
76419304Speter	 * XXX
76519304Speter	 * file_init() is going to set ep->rcv_path.
76619304Speter	 */
767254225Speter	if (file_init(sp, frp, pathp, 0)) {
76819304Speter		free(recp);
76919304Speter		free(pathp);
77019304Speter		(void)close(sv_fd);
77119304Speter		return (1);
77219304Speter	}
773254225Speter	free(pathp);
77419304Speter
77519304Speter	/*
77619304Speter	 * We keep an open lock on the file so that the recover option can
77719304Speter	 * distinguish between files that are live and those that need to
77819304Speter	 * be recovered.  The lock is already acquired, just copy it.
77919304Speter	 */
78019304Speter	ep = sp->ep;
78119304Speter	ep->rcv_mpath = recp;
78219304Speter	ep->rcv_fd = sv_fd;
78319304Speter	if (!locked)
78419304Speter		F_SET(frp, FR_UNLOCKED);
78519304Speter
78619304Speter	/* We believe the file is recoverable. */
78719304Speter	F_SET(ep, F_RCV_ON);
78819304Speter	return (0);
78919304Speter}
79019304Speter
79119304Speter/*
79219304Speter * rcv_copy --
79319304Speter *	Copy a recovery file.
79419304Speter */
79519304Speterstatic int
796254225Speterrcv_copy(
797254225Speter	SCR *sp,
798254225Speter	int wfd,
799254225Speter	char *fname)
80019304Speter{
80119304Speter	int nr, nw, off, rfd;
80219304Speter	char buf[8 * 1024];
80319304Speter
80419304Speter	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
80519304Speter		goto err;
80619304Speter	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
80719304Speter		for (off = 0; nr; nr -= nw, off += nw)
80819304Speter			if ((nw = write(wfd, buf + off, nr)) < 0)
80919304Speter				goto err;
81019304Speter	if (nr == 0)
81119304Speter		return (0);
81219304Speter
81319304Spetererr:	msgq_str(sp, M_SYSERR, fname, "%s");
81419304Speter	return (1);
81519304Speter}
81619304Speter
81719304Speter/*
81819304Speter * rcv_mktemp --
81919304Speter *	Paranoid make temporary file routine.
82019304Speter */
82119304Speterstatic int
822254225Speterrcv_mktemp(
823254225Speter	SCR *sp,
824254225Speter	char *path,
825254225Speter	char *dname)
82619304Speter{
82719304Speter	int fd;
82819304Speter
82919304Speter	if ((fd = mkstemp(path)) == -1)
83019304Speter		msgq_str(sp, M_SYSERR, dname, "%s");
83119304Speter	return (fd);
83219304Speter}
83319304Speter
83419304Speter/*
83519304Speter * rcv_email --
83619304Speter *	Send email.
83719304Speter */
83819304Speterstatic void
839254225Speterrcv_email(
840254225Speter	SCR *sp,
841254225Speter	char *fname)
84219304Speter{
843254225Speter	char *buf;
84419304Speter
845254225Speter	(void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname);
846254225Speter	if (buf == NULL) {
847254225Speter		msgq_str(sp, M_ERR, strerror(errno),
848254225Speter		    "071|not sending email: %s");
849254225Speter		return;
85019304Speter	}
851254225Speter	(void)system(buf);
852254225Speter	free(buf);
85319304Speter}
854254225Speter
855254225Speter/*
856254225Speter * rcv_dlnwrite --
857254225Speter *	Encode a string into an X-vi-data line and write it.
858254225Speter */
859254225Speterstatic int
860254225Speterrcv_dlnwrite(
861254225Speter	SCR *sp,
862254225Speter	const char *dtype,
863254225Speter	const char *src,
864254225Speter	FILE *fp)
865254225Speter{
866254225Speter	char *bp = NULL, *p;
867254225Speter	size_t blen = 0;
868254225Speter	size_t dlen, len;
869254225Speter	int plen, xlen;
870254225Speter
871254225Speter	len = strlen(src);
872254225Speter	dlen = strlen(dtype);
873254225Speter	GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2);
874254225Speter	(void)memcpy(bp, dtype, dlen);
875254225Speter	bp[dlen] = ';';
876254225Speter	if ((xlen = b64_ntop((u_char *)src,
877254225Speter	    len, bp + dlen + 1, blen)) == -1)
878254225Speter		goto err;
879254225Speter	xlen += dlen + 1;
880254225Speter
881254225Speter	/* Output as an MIME folding header. */
882254225Speter	if ((plen = fprintf(fp, VI_DHEADER " %.*s\n",
883254225Speter	    FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0)
884254225Speter		goto err;
885254225Speter	plen -= (int)sizeof(VI_DHEADER) + 1;
886254225Speter	for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) {
887254225Speter		p += plen;
888254225Speter		if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0)
889254225Speter			goto err;
890254225Speter		plen -= 2;
891254225Speter	}
892254225Speter	FREE_SPACE(sp, bp, blen);
893254225Speter	return (0);
894254225Speter
895254225Spetererr:	FREE_SPACE(sp, bp, blen);
896254225Speter	return (1);
897254225Speteralloc_err:
898254225Speter	msgq(sp, M_SYSERR, NULL);
899254225Speter	return (-1);
900254225Speter}
901254225Speter
902254225Speter/*
903254225Speter * rcv_dlnread --
904254225Speter *	Read an X-vi-data line and decode it.
905254225Speter */
906254225Speterstatic int
907254225Speterrcv_dlnread(
908254225Speter	SCR *sp,
909254225Speter	char **dtypep,
910254225Speter	char **datap,		/* free *datap if != NULL after use. */
911254225Speter	FILE *fp)
912254225Speter{
913254225Speter	int ch;
914254225Speter	char buf[1024];
915254225Speter	char *bp = NULL, *p, *src;
916254225Speter	size_t blen = 0;
917254225Speter	size_t len, off, dlen;
918254225Speter	char *dtype, *data;
919254225Speter	int xlen;
920254225Speter
921254225Speter	if (fgets(buf, sizeof(buf), fp) == NULL)
922254225Speter		return (1);
923254225Speter	if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) {
924254225Speter		*dtypep = NULL;
925254225Speter		*datap = NULL;
926254225Speter		return (0);
927254225Speter	}
928254225Speter
929254225Speter	/* Fetch an MIME folding header. */
930254225Speter	len = strlen(buf) - sizeof(VI_DHEADER) + 1;
931254225Speter	GET_SPACE_GOTOC(sp, bp, blen, len);
932254225Speter	(void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len);
933254225Speter	p = bp + len;
934254225Speter	while ((ch = fgetc(fp)) == ' ') {
935254225Speter		if (fgets(buf, sizeof(buf), fp) == NULL)
936254225Speter			goto err;
937254225Speter		off = strlen(buf);
938254225Speter		len += off;
939254225Speter		ADD_SPACE_GOTOC(sp, bp, blen, len);
940254225Speter		p = bp + len - off;
941254225Speter		(void)memcpy(p, buf, off);
942254225Speter	}
943254225Speter	bp[len] = '\0';
944254225Speter	(void)ungetc(ch, fp);
945254225Speter
946254225Speter	for (p = bp; *p == ' ' || *p == '\n'; p++);
947254225Speter	if ((src = strchr(p, ';')) == NULL)
948254225Speter		goto err;
949254225Speter	dlen = src - p;
950254225Speter	src += 1;
951254225Speter	len -= src - bp;
952254225Speter
953254225Speter	/* Memory looks like: "<data>\0<dtype>\0". */
954254225Speter	MALLOC(sp, data, char *, dlen + len / 4 * 3 + 2);
955254225Speter	if (data == NULL)
956254225Speter		goto err;
957254225Speter	if ((xlen = (b64_pton(p + dlen + 1,
958254225Speter	    (u_char *)data, len / 4 * 3 + 1))) == -1) {
959254225Speter		free(data);
960254225Speter		goto err;
961254225Speter	}
962254225Speter	data[xlen] = '\0';
963254225Speter	dtype = data + xlen + 1;
964254225Speter	(void)memcpy(dtype, p, dlen);
965254225Speter	dtype[dlen] = '\0';
966254225Speter	FREE_SPACE(sp, bp, blen);
967254225Speter	*dtypep = dtype;
968254225Speter	*datap = data;
969254225Speter	return (0);
970254225Speter
971254225Spetererr: 	FREE_SPACE(sp, bp, blen);
972254225Speter	return (1);
973254225Speteralloc_err:
974254225Speter	msgq(sp, M_SYSERR, NULL);
975254225Speter	return (-1);
976254225Speter}
977