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
1319304Speterstatic const char sccsid[] = "@(#)recover.c	10.21 (Berkeley) 9/15/96";
1419304Speter#endif /* not lint */
1519304Speter
1619304Speter#include <sys/param.h>
1719304Speter#include <sys/types.h>		/* XXX: param.h may not have included types.h */
1819304Speter#include <sys/queue.h>
1919304Speter#include <sys/stat.h>
2019304Speter
2119304Speter/*
2219304Speter * We include <sys/file.h>, because the open #defines were found there
2319304Speter * on historical systems.  We also include <fcntl.h> because the open(2)
2419304Speter * #defines are found there on newer systems.
2519304Speter */
2619304Speter#include <sys/file.h>
2719304Speter
2819304Speter#include <bitstring.h>
2919304Speter#include <dirent.h>
3019304Speter#include <errno.h>
3119304Speter#include <fcntl.h>
3219304Speter#include <limits.h>
3319304Speter#include <pwd.h>
3419304Speter#include <stdio.h>
3519304Speter#include <stdlib.h>
3619304Speter#include <string.h>
3719304Speter#include <time.h>
3819304Speter#include <unistd.h>
3919304Speter
4019304Speter#include "common.h"
4119304Speter#include "pathnames.h"
4219304Speter
4319304Speter/*
4419304Speter * Recovery code.
4519304Speter *
4619304Speter * The basic scheme is as follows.  In the EXF structure, we maintain full
4719304Speter * paths of a b+tree file and a mail recovery file.  The former is the file
4819304Speter * used as backing store by the DB package.  The latter is the file that
4919304Speter * contains an email message to be sent to the user if we crash.  The two
5019304Speter * simple states of recovery are:
5119304Speter *
5219304Speter *	+ first starting the edit session:
5319304Speter *		the b+tree file exists and is mode 700, the mail recovery
5419304Speter *		file doesn't exist.
5519304Speter *	+ after the file has been modified:
5619304Speter *		the b+tree file exists and is mode 600, the mail recovery
5719304Speter *		file exists, and is exclusively locked.
5819304Speter *
5919304Speter * In the EXF structure we maintain a file descriptor that is the locked
6019304Speter * file descriptor for the mail recovery file.  NOTE: we sometimes have to
6119304Speter * do locking with fcntl(2).  This is a problem because if you close(2) any
6219304Speter * file descriptor associated with the file, ALL of the locks go away.  Be
6319304Speter * sure to remember that if you have to modify the recovery code.  (It has
6419304Speter * been rhetorically asked of what the designers could have been thinking
6519304Speter * when they did that interface.  The answer is simple: they weren't.)
6619304Speter *
6719304Speter * To find out if a recovery file/backing file pair are in use, try to get
6819304Speter * a lock on the recovery file.
6919304Speter *
7019304Speter * To find out if a backing file can be deleted at boot time, check for an
7119304Speter * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
7219304Speter * special stuff into the backing file itself, or correlate the files at
7319304Speter * boot time, neither of which looks like fun.)  Note also that there's a
7419304Speter * window between when the file is created and the X bit is set.  It's small,
7519304Speter * but it's there.  To fix the window, check for 0 length files as well.
7619304Speter *
7719304Speter * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
7819304Speter * this DOES NOT mean that any initialization has been done, only that we
7919304Speter * haven't yet failed at setting up or doing recovery.
8019304Speter *
8119304Speter * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
8219304Speter * If that bit is not set when ending a file session:
8319304Speter *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
8419304Speter *	they are unlink(2)'d, and free(3)'d.
8519304Speter *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
8619304Speter *
8719304Speter * The backing b+tree file is set up when a file is first edited, so that
8819304Speter * the DB package can use it for on-disk caching and/or to snapshot the
8919304Speter * file.  When the file is first modified, the mail recovery file is created,
9019304Speter * the backing file permissions are updated, the file is sync(2)'d to disk,
9119304Speter * and the timer is started.  Then, at RCV_PERIOD second intervals, the
9219304Speter * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
9319304Speter * means that the data structures (SCR, EXF, the underlying tree structures)
9419304Speter * must be consistent when the signal arrives.
9519304Speter *
9619304Speter * The recovery mail file contains normal mail headers, with two additions,
9719304Speter * which occur in THIS order, as the FIRST TWO headers:
9819304Speter *
9919304Speter *	X-vi-recover-file: file_name
10019304Speter *	X-vi-recover-path: recover_path
10119304Speter *
10219304Speter * Since newlines delimit the headers, this means that file names cannot have
10319304Speter * newlines in them, but that's probably okay.  As these files aren't intended
10419304Speter * to be long-lived, changing their format won't be too painful.
10519304Speter *
10619304Speter * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
10719304Speter */
10819304Speter
10919304Speter#define	VI_FHEADER	"X-vi-recover-file: "
11019304Speter#define	VI_PHEADER	"X-vi-recover-path: "
11119304Speter
11219304Speterstatic int	 rcv_copy __P((SCR *, int, char *));
11319304Speterstatic void	 rcv_email __P((SCR *, char *));
11419304Speterstatic char	*rcv_gets __P((char *, size_t, int));
11519304Speterstatic int	 rcv_mailfile __P((SCR *, int, char *));
11619304Speterstatic int	 rcv_mktemp __P((SCR *, char *, char *, int));
11719304Speter
11819304Speter/*
11919304Speter * rcv_tmp --
12019304Speter *	Build a file name that will be used as the recovery file.
12119304Speter *
12219304Speter * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
12319304Speter */
12419304Speterint
12519304Speterrcv_tmp(sp, ep, name)
12619304Speter	SCR *sp;
12719304Speter	EXF *ep;
12819304Speter	char *name;
12919304Speter{
13019304Speter	struct stat sb;
13119304Speter	int fd;
13219304Speter	char *dp, *p, path[MAXPATHLEN];
13319304Speter
13419304Speter	/*
13519304Speter	 * !!!
13619304Speter	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
13719304Speter	 *
13819304Speter	 *
13919304Speter	 * If the recovery directory doesn't exist, try and create it.  As
14019304Speter	 * the recovery files are themselves protected from reading/writing
14119304Speter	 * by other than the owner, the worst that can happen is that a user
14219304Speter	 * would have permission to remove other user's recovery files.  If
14319304Speter	 * the sticky bit has the BSD semantics, that too will be impossible.
14419304Speter	 */
14519304Speter	if (opts_empty(sp, O_RECDIR, 0))
14619304Speter		goto err;
14719304Speter	dp = O_STR(sp, O_RECDIR);
14819304Speter	if (stat(dp, &sb)) {
14919304Speter		if (errno != ENOENT || mkdir(dp, 0)) {
15019304Speter			msgq(sp, M_SYSERR, "%s", dp);
15119304Speter			goto err;
15219304Speter		}
15319304Speter		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
15419304Speter	}
15519304Speter
15619304Speter	/* Newlines delimit the mail messages. */
15719304Speter	for (p = name; *p; ++p)
15819304Speter		if (*p == '\n') {
15919304Speter			msgq(sp, M_ERR,
16019304Speter		    "055|Files with newlines in the name are unrecoverable");
16119304Speter			goto err;
16219304Speter		}
16319304Speter
16419304Speter	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
16519304Speter	if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
16619304Speter		goto err;
16719304Speter	(void)close(fd);
16819304Speter
16919304Speter	if ((ep->rcv_path = strdup(path)) == NULL) {
17019304Speter		msgq(sp, M_SYSERR, NULL);
17119304Speter		(void)unlink(path);
17219304Spetererr:		msgq(sp, M_ERR,
17319304Speter		    "056|Modifications not recoverable if the session fails");
17419304Speter		return (1);
17519304Speter	}
17619304Speter
17719304Speter	/* We believe the file is recoverable. */
17819304Speter	F_SET(ep, F_RCV_ON);
17919304Speter	return (0);
18019304Speter}
18119304Speter
18219304Speter/*
18319304Speter * rcv_init --
18419304Speter *	Force the file to be snapshotted for recovery.
18519304Speter *
18619304Speter * PUBLIC: int rcv_init __P((SCR *));
18719304Speter */
18819304Speterint
18919304Speterrcv_init(sp)
19019304Speter	SCR *sp;
19119304Speter{
19219304Speter	EXF *ep;
19319304Speter	recno_t lno;
19419304Speter
19519304Speter	ep = sp->ep;
19619304Speter
19719304Speter	/* Only do this once. */
19819304Speter	F_CLR(ep, F_FIRSTMODIFY);
19919304Speter
20019304Speter	/* If we already know the file isn't recoverable, we're done. */
20119304Speter	if (!F_ISSET(ep, F_RCV_ON))
20219304Speter		return (0);
20319304Speter
20419304Speter	/* Turn off recoverability until we figure out if this will work. */
20519304Speter	F_CLR(ep, F_RCV_ON);
20619304Speter
20719304Speter	/* Test if we're recovering a file, not editing one. */
20819304Speter	if (ep->rcv_mpath == NULL) {
20919304Speter		/* Build a file to mail to the user. */
21019304Speter		if (rcv_mailfile(sp, 0, NULL))
21119304Speter			goto err;
21219304Speter
21319304Speter		/* Force a read of the entire file. */
21419304Speter		if (db_last(sp, &lno))
21519304Speter			goto err;
21619304Speter
21719304Speter		/* Turn on a busy message, and sync it to backing store. */
21819304Speter		sp->gp->scr_busy(sp,
21919304Speter		    "057|Copying file for recovery...", BUSY_ON);
22019304Speter		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
22119304Speter			msgq_str(sp, M_SYSERR, ep->rcv_path,
22219304Speter			    "058|Preservation failed: %s");
22319304Speter			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
22419304Speter			goto err;
22519304Speter		}
22619304Speter		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
22719304Speter	}
22819304Speter
22919304Speter	/* Turn off the owner execute bit. */
23019304Speter	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
23119304Speter
23219304Speter	/* We believe the file is recoverable. */
23319304Speter	F_SET(ep, F_RCV_ON);
23419304Speter	return (0);
23519304Speter
23619304Spetererr:	msgq(sp, M_ERR,
23719304Speter	    "059|Modifications not recoverable if the session fails");
23819304Speter	return (1);
23919304Speter}
24019304Speter
24119304Speter/*
24219304Speter * rcv_sync --
24319304Speter *	Sync the file, optionally:
24419304Speter *		flagging the backup file to be preserved
24519304Speter *		snapshotting the backup file and send email to the user
24619304Speter *		sending email to the user if the file was modified
24719304Speter *		ending the file session
24819304Speter *
24919304Speter * PUBLIC: int rcv_sync __P((SCR *, u_int));
25019304Speter */
25119304Speterint
25219304Speterrcv_sync(sp, flags)
25319304Speter	SCR *sp;
25419304Speter	u_int flags;
25519304Speter{
25619304Speter	EXF *ep;
25719304Speter	int fd, rval;
25819304Speter	char *dp, buf[1024];
25919304Speter
26019304Speter	/* Make sure that there's something to recover/sync. */
26119304Speter	ep = sp->ep;
26219304Speter	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
26319304Speter		return (0);
26419304Speter
26519304Speter	/* Sync the file if it's been modified. */
26619304Speter	if (F_ISSET(ep, F_MODIFIED)) {
26719304Speter		SIGBLOCK;
26819304Speter		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
26919304Speter			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
27019304Speter			msgq_str(sp, M_SYSERR,
27119304Speter			    ep->rcv_path, "060|File backup failed: %s");
27219304Speter			SIGUNBLOCK;
27319304Speter			return (1);
27419304Speter		}
27519304Speter		SIGUNBLOCK;
27619304Speter
27719304Speter		/* REQUEST: don't remove backing file on exit. */
27819304Speter		if (LF_ISSET(RCV_PRESERVE))
27919304Speter			F_SET(ep, F_RCV_NORM);
28019304Speter
28119304Speter		/* REQUEST: send email. */
28219304Speter		if (LF_ISSET(RCV_EMAIL))
28319304Speter			rcv_email(sp, ep->rcv_mpath);
28419304Speter	}
28519304Speter
28619304Speter	/*
28719304Speter	 * !!!
28819304Speter	 * Each time the user exec's :preserve, we have to snapshot all of
28919304Speter	 * the recovery information, i.e. it's like the user re-edited the
29019304Speter	 * file.  We copy the DB(3) backing file, and then create a new mail
29119304Speter	 * recovery file, it's simpler than exiting and reopening all of the
29219304Speter	 * underlying files.
29319304Speter	 *
29419304Speter	 * REQUEST: snapshot the file.
29519304Speter	 */
29619304Speter	rval = 0;
29719304Speter	if (LF_ISSET(RCV_SNAPSHOT)) {
29819304Speter		if (opts_empty(sp, O_RECDIR, 0))
29919304Speter			goto err;
30019304Speter		dp = O_STR(sp, O_RECDIR);
30119304Speter		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
30219304Speter		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
30319304Speter			goto err;
30419304Speter		sp->gp->scr_busy(sp,
30519304Speter		    "061|Copying file for recovery...", BUSY_ON);
30619304Speter		if (rcv_copy(sp, fd, ep->rcv_path) ||
30719304Speter		    close(fd) || rcv_mailfile(sp, 1, buf)) {
30819304Speter			(void)unlink(buf);
30919304Speter			(void)close(fd);
31019304Speter			rval = 1;
31119304Speter		}
31219304Speter		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
31319304Speter	}
31419304Speter	if (0) {
31519304Spetererr:		rval = 1;
31619304Speter	}
31719304Speter
31819304Speter	/* REQUEST: end the file session. */
31919304Speter	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
32019304Speter		rval = 1;
32119304Speter
32219304Speter	return (rval);
32319304Speter}
32419304Speter
32519304Speter/*
32619304Speter * rcv_mailfile --
32719304Speter *	Build the file to mail to the user.
32819304Speter */
32919304Speterstatic int
33019304Speterrcv_mailfile(sp, issync, cp_path)
33119304Speter	SCR *sp;
33219304Speter	int issync;
33319304Speter	char *cp_path;
33419304Speter{
33519304Speter	EXF *ep;
33619304Speter	GS *gp;
33719304Speter	struct passwd *pw;
33819304Speter	size_t len;
33919304Speter	time_t now;
34019304Speter	uid_t uid;
34119304Speter	int fd;
34219304Speter	char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
34319304Speter	char *t1, *t2, *t3;
34419304Speter
34519304Speter	/*
34619304Speter	 * XXX
34719304Speter	 * MAXHOSTNAMELEN is in various places on various systems, including
34819304Speter	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
34919304Speter	 */
35019304Speter#ifndef MAXHOSTNAMELEN
35119304Speter#define	MAXHOSTNAMELEN	1024
35219304Speter#endif
35319304Speter	char host[MAXHOSTNAMELEN];
35419304Speter
35519304Speter	gp = sp->gp;
35619304Speter	if ((pw = getpwuid(uid = getuid())) == NULL) {
35719304Speter		msgq(sp, M_ERR,
35819304Speter		    "062|Information on user id %u not found", uid);
35919304Speter		return (1);
36019304Speter	}
36119304Speter
36219304Speter	if (opts_empty(sp, O_RECDIR, 0))
36319304Speter		return (1);
36419304Speter	dp = O_STR(sp, O_RECDIR);
36519304Speter	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
36619304Speter	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
36719304Speter		return (1);
36819304Speter
36919304Speter	/*
37019304Speter	 * XXX
37119304Speter	 * We keep an open lock on the file so that the recover option can
37219304Speter	 * distinguish between files that are live and those that need to
37319304Speter	 * be recovered.  There's an obvious window between the mkstemp call
37419304Speter	 * and the lock, but it's pretty small.
37519304Speter	 */
37619304Speter	ep = sp->ep;
37719304Speter	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
37819304Speter		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
37919304Speter	if (!issync) {
38019304Speter		/* Save the recover file descriptor, and mail path. */
38119304Speter		ep->rcv_fd = fd;
38219304Speter		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
38319304Speter			msgq(sp, M_SYSERR, NULL);
38419304Speter			goto err;
38519304Speter		}
38619304Speter		cp_path = ep->rcv_path;
38719304Speter	}
38819304Speter
38919304Speter	/*
39019304Speter	 * XXX
39119304Speter	 * We can't use stdio(3) here.  The problem is that we may be using
39219304Speter	 * fcntl(2), so if ANY file descriptor into the file is closed, the
39319304Speter	 * lock is lost.  So, we could never close the FILE *, even if we
39419304Speter	 * dup'd the fd first.
39519304Speter	 */
39619304Speter	t = sp->frp->name;
39719304Speter	if ((p = strrchr(t, '/')) == NULL)
39819304Speter		p = t;
39919304Speter	else
40019304Speter		++p;
40119304Speter	(void)time(&now);
40219304Speter	(void)gethostname(host, sizeof(host));
40319304Speter	len = snprintf(buf, sizeof(buf),
40419304Speter	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
40519304Speter	    VI_FHEADER, t,			/* Non-standard. */
40619304Speter	    VI_PHEADER, cp_path,		/* Non-standard. */
40719304Speter	    "Reply-To: root",
40819304Speter	    "From: root (Nvi recovery program)",
40919304Speter	    "To: ", pw->pw_name,
41019304Speter	    "Subject: Nvi saved the file ", p,
41119304Speter	    "Precedence: bulk");		/* For vacation(1). */
41219304Speter	if (len > sizeof(buf) - 1)
41319304Speter		goto lerr;
41419304Speter	if (write(fd, buf, len) != len)
41519304Speter		goto werr;
41619304Speter
41719304Speter	len = snprintf(buf, sizeof(buf),
41819304Speter	    "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
41919304Speter	    "On ", ctime(&now), ", the user ", pw->pw_name,
42019304Speter	    " was editing a file named ", t, " on the machine ",
42119304Speter	    host, ", when it was saved for recovery. ",
42219304Speter	    "You can recover most, if not all, of the changes ",
42319304Speter	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
42419304Speter	    gp->progname, " -r ", t);
42519304Speter	if (len > sizeof(buf) - 1) {
42619304Speterlerr:		msgq(sp, M_ERR, "064|Recovery file buffer overrun");
42719304Speter		goto err;
42819304Speter	}
42919304Speter
43019304Speter	/*
43119304Speter	 * Format the message.  (Yes, I know it's silly.)
43219304Speter	 * Requires that the message end in a <newline>.
43319304Speter	 */
43419304Speter#define	FMTCOLS	60
43519304Speter	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
43619304Speter		/* Check for a short length. */
43719304Speter		if (len <= FMTCOLS) {
43819304Speter			t2 = t1 + (len - 1);
43919304Speter			goto wout;
44019304Speter		}
44119304Speter
44219304Speter		/* Check for a required <newline>. */
44319304Speter		t2 = strchr(t1, '\n');
44419304Speter		if (t2 - t1 <= FMTCOLS)
44519304Speter			goto wout;
44619304Speter
44719304Speter		/* Find the closest space, if any. */
44819304Speter		for (t3 = t2; t2 > t1; --t2)
44919304Speter			if (*t2 == ' ') {
45019304Speter				if (t2 - t1 <= FMTCOLS)
45119304Speter					goto wout;
45219304Speter				t3 = t2;
45319304Speter			}
45419304Speter		t2 = t3;
45519304Speter
45619304Speter		/* t2 points to the last character to display. */
45719304Speterwout:		*t2++ = '\n';
45819304Speter
45919304Speter		/* t2 points one after the last character to display. */
46019304Speter		if (write(fd, t1, t2 - t1) != t2 - t1)
46119304Speter			goto werr;
46219304Speter	}
46319304Speter
46419304Speter	if (issync) {
46519304Speter		rcv_email(sp, mpath);
46619304Speter		if (close(fd)) {
46719304Speterwerr:			msgq(sp, M_SYSERR, "065|Recovery file");
46819304Speter			goto err;
46919304Speter		}
47019304Speter	}
47119304Speter	return (0);
47219304Speter
47319304Spetererr:	if (!issync)
47419304Speter		ep->rcv_fd = -1;
47519304Speter	if (fd != -1)
47619304Speter		(void)close(fd);
47719304Speter	return (1);
47819304Speter}
47919304Speter
48019304Speter/*
48119304Speter *	people making love
48219304Speter *	never exactly the same
48319304Speter *	just like a snowflake
48419304Speter *
48519304Speter * rcv_list --
48619304Speter *	List the files that can be recovered by this user.
48719304Speter *
48819304Speter * PUBLIC: int rcv_list __P((SCR *));
48919304Speter */
49019304Speterint
49119304Speterrcv_list(sp)
49219304Speter	SCR *sp;
49319304Speter{
49419304Speter	struct dirent *dp;
49519304Speter	struct stat sb;
49619304Speter	DIR *dirp;
49719304Speter	FILE *fp;
49819304Speter	int found;
49919304Speter	char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
50019304Speter
50119304Speter	/* Open the recovery directory for reading. */
50219304Speter	if (opts_empty(sp, O_RECDIR, 0))
50319304Speter		return (1);
50419304Speter	p = O_STR(sp, O_RECDIR);
50519304Speter	if (chdir(p) || (dirp = opendir(".")) == NULL) {
50619304Speter		msgq_str(sp, M_SYSERR, p, "recdir: %s");
50719304Speter		return (1);
50819304Speter	}
50919304Speter
51019304Speter	/* Read the directory. */
51119304Speter	for (found = 0; (dp = readdir(dirp)) != NULL;) {
51219304Speter		if (strncmp(dp->d_name, "recover.", 8))
51319304Speter			continue;
51419304Speter
51519304Speter		/*
51619304Speter		 * If it's readable, it's recoverable.
51719304Speter		 *
51819304Speter		 * XXX
51919304Speter		 * Should be "r", we don't want to write the file.  However,
52019304Speter		 * if we're using fcntl(2), there's no way to lock a file
52119304Speter		 * descriptor that's not open for writing.
52219304Speter		 */
52319304Speter		if ((fp = fopen(dp->d_name, "r+")) == NULL)
52419304Speter			continue;
52519304Speter
52619304Speter		switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
52719304Speter		case LOCK_FAILED:
52819304Speter			/*
52919304Speter			 * XXX
53019304Speter			 * Assume that a lock can't be acquired, but that we
53119304Speter			 * should permit recovery anyway.  If this is wrong,
53219304Speter			 * and someone else is using the file, we're going to
53319304Speter			 * die horribly.
53419304Speter			 */
53519304Speter			break;
53619304Speter		case LOCK_SUCCESS:
53719304Speter			break;
53819304Speter		case LOCK_UNAVAIL:
53919304Speter			/* If it's locked, it's live. */
54019304Speter			(void)fclose(fp);
54119304Speter			continue;
54219304Speter		}
54319304Speter
54419304Speter		/* Check the headers. */
54519304Speter		if (fgets(file, sizeof(file), fp) == NULL ||
54619304Speter		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
54719304Speter		    (p = strchr(file, '\n')) == NULL ||
54819304Speter		    fgets(path, sizeof(path), fp) == NULL ||
54919304Speter		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
55019304Speter		    (t = strchr(path, '\n')) == NULL) {
55119304Speter			msgq_str(sp, M_ERR, dp->d_name,
55219304Speter			    "066|%s: malformed recovery file");
55319304Speter			goto next;
55419304Speter		}
55519304Speter		*p = *t = '\0';
55619304Speter
55719304Speter		/*
55819304Speter		 * If the file doesn't exist, it's an orphaned recovery file,
55919304Speter		 * toss it.
56019304Speter		 *
56119304Speter		 * XXX
56219304Speter		 * This can occur if the backup file was deleted and we crashed
56319304Speter		 * before deleting the email file.
56419304Speter		 */
56519304Speter		errno = 0;
56619304Speter		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
56719304Speter		    errno == ENOENT) {
56819304Speter			(void)unlink(dp->d_name);
56919304Speter			goto next;
57019304Speter		}
57119304Speter
57219304Speter		/* Get the last modification time and display. */
57319304Speter		(void)fstat(fileno(fp), &sb);
57419304Speter		(void)printf("%.24s: %s\n",
57519304Speter		    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
57619304Speter		found = 1;
57719304Speter
57819304Speter		/* Close, discarding lock. */
57919304Speternext:		(void)fclose(fp);
58019304Speter	}
58119304Speter	if (found == 0)
58219304Speter		(void)printf("vi: no files to recover.\n");
58319304Speter	(void)closedir(dirp);
58419304Speter	return (0);
58519304Speter}
58619304Speter
58719304Speter/*
58819304Speter * rcv_read --
58919304Speter *	Start a recovered file as the file to edit.
59019304Speter *
59119304Speter * PUBLIC: int rcv_read __P((SCR *, FREF *));
59219304Speter */
59319304Speterint
59419304Speterrcv_read(sp, frp)
59519304Speter	SCR *sp;
59619304Speter	FREF *frp;
59719304Speter{
59819304Speter	struct dirent *dp;
59919304Speter	struct stat sb;
60019304Speter	DIR *dirp;
60119304Speter	EXF *ep;
60219304Speter	time_t rec_mtime;
60319304Speter	int fd, found, locked, requested, sv_fd;
60419304Speter	char *name, *p, *t, *rp, *recp, *pathp;
60519304Speter	char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
60619304Speter
60719304Speter	if (opts_empty(sp, O_RECDIR, 0))
60819304Speter		return (1);
60919304Speter	rp = O_STR(sp, O_RECDIR);
61019304Speter	if ((dirp = opendir(rp)) == NULL) {
61119304Speter		msgq_str(sp, M_ERR, rp, "%s");
61219304Speter		return (1);
61319304Speter	}
61419304Speter
61519304Speter	name = frp->name;
61619304Speter	sv_fd = -1;
61719304Speter	rec_mtime = 0;
61819304Speter	recp = pathp = NULL;
61919304Speter	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
62019304Speter		if (strncmp(dp->d_name, "recover.", 8))
62119304Speter			continue;
62219304Speter		(void)snprintf(recpath,
62319304Speter		    sizeof(recpath), "%s/%s", rp, dp->d_name);
62419304Speter
62519304Speter		/*
62619304Speter		 * If it's readable, it's recoverable.  It would be very
62719304Speter		 * nice to use stdio(3), but, we can't because that would
62819304Speter		 * require closing and then reopening the file so that we
62919304Speter		 * could have a lock and still close the FP.  Another tip
63019304Speter		 * of the hat to fcntl(2).
63119304Speter		 *
63219304Speter		 * XXX
63319304Speter		 * Should be O_RDONLY, we don't want to write it.  However,
63419304Speter		 * if we're using fcntl(2), there's no way to lock a file
63519304Speter		 * descriptor that's not open for writing.
63619304Speter		 */
63719304Speter		if ((fd = open(recpath, O_RDWR, 0)) == -1)
63819304Speter			continue;
63919304Speter
64019304Speter		switch (file_lock(sp, NULL, NULL, fd, 1)) {
64119304Speter		case LOCK_FAILED:
64219304Speter			/*
64319304Speter			 * XXX
64419304Speter			 * Assume that a lock can't be acquired, but that we
64519304Speter			 * should permit recovery anyway.  If this is wrong,
64619304Speter			 * and someone else is using the file, we're going to
64719304Speter			 * die horribly.
64819304Speter			 */
64919304Speter			locked = 0;
65019304Speter			break;
65119304Speter		case LOCK_SUCCESS:
65219304Speter			locked = 1;
65319304Speter			break;
65419304Speter		case LOCK_UNAVAIL:
65519304Speter			/* If it's locked, it's live. */
65619304Speter			(void)close(fd);
65719304Speter			continue;
65819304Speter		}
65919304Speter
66019304Speter		/* Check the headers. */
66119304Speter		if (rcv_gets(file, sizeof(file), fd) == NULL ||
66219304Speter		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
66319304Speter		    (p = strchr(file, '\n')) == NULL ||
66419304Speter		    rcv_gets(path, sizeof(path), fd) == NULL ||
66519304Speter		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
66619304Speter		    (t = strchr(path, '\n')) == NULL) {
66719304Speter			msgq_str(sp, M_ERR, recpath,
66819304Speter			    "067|%s: malformed recovery file");
66919304Speter			goto next;
67019304Speter		}
67119304Speter		*p = *t = '\0';
67219304Speter		++found;
67319304Speter
67419304Speter		/*
67519304Speter		 * If the file doesn't exist, it's an orphaned recovery file,
67619304Speter		 * toss it.
67719304Speter		 *
67819304Speter		 * XXX
67919304Speter		 * This can occur if the backup file was deleted and we crashed
68019304Speter		 * before deleting the email file.
68119304Speter		 */
68219304Speter		errno = 0;
68319304Speter		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
68419304Speter		    errno == ENOENT) {
68519304Speter			(void)unlink(dp->d_name);
68619304Speter			goto next;
68719304Speter		}
68819304Speter
68919304Speter		/* Check the file name. */
69019304Speter		if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
69119304Speter			goto next;
69219304Speter
69319304Speter		++requested;
69419304Speter
69519304Speter		/*
69619304Speter		 * If we've found more than one, take the most recent.
69719304Speter		 *
69819304Speter		 * XXX
69919304Speter		 * Since we're using st_mtime, for portability reasons,
70019304Speter		 * we only get a single second granularity, instead of
70119304Speter		 * getting it right.
70219304Speter		 */
70319304Speter		(void)fstat(fd, &sb);
70419304Speter		if (recp == NULL || rec_mtime < sb.st_mtime) {
70519304Speter			p = recp;
70619304Speter			t = pathp;
70719304Speter			if ((recp = strdup(recpath)) == NULL) {
70819304Speter				msgq(sp, M_SYSERR, NULL);
70919304Speter				recp = p;
71019304Speter				goto next;
71119304Speter			}
71219304Speter			if ((pathp = strdup(path)) == NULL) {
71319304Speter				msgq(sp, M_SYSERR, NULL);
71419304Speter				free(recp);
71519304Speter				recp = p;
71619304Speter				pathp = t;
71719304Speter				goto next;
71819304Speter			}
71919304Speter			if (p != NULL) {
72019304Speter				free(p);
72119304Speter				free(t);
72219304Speter			}
72319304Speter			rec_mtime = sb.st_mtime;
72419304Speter			if (sv_fd != -1)
72519304Speter				(void)close(sv_fd);
72619304Speter			sv_fd = fd;
72719304Speter		} else
72819304Speternext:			(void)close(fd);
72919304Speter	}
73019304Speter	(void)closedir(dirp);
73119304Speter
73219304Speter	if (recp == NULL) {
73319304Speter		msgq_str(sp, M_INFO, name,
73419304Speter		    "068|No files named %s, readable by you, to recover");
73519304Speter		return (1);
73619304Speter	}
73719304Speter	if (found) {
73819304Speter		if (requested > 1)
73919304Speter			msgq(sp, M_INFO,
74019304Speter	    "069|There are older versions of this file for you to recover");
74119304Speter		if (found > requested)
74219304Speter			msgq(sp, M_INFO,
74319304Speter			    "070|There are other files for you to recover");
74419304Speter	}
74519304Speter
74619304Speter	/*
74719304Speter	 * Create the FREF structure, start the btree file.
74819304Speter	 *
74919304Speter	 * XXX
75019304Speter	 * file_init() is going to set ep->rcv_path.
75119304Speter	 */
75219304Speter	if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
75319304Speter		free(recp);
75419304Speter		free(pathp);
75519304Speter		(void)close(sv_fd);
75619304Speter		return (1);
75719304Speter	}
75819304Speter
75919304Speter	/*
76019304Speter	 * We keep an open lock on the file so that the recover option can
76119304Speter	 * distinguish between files that are live and those that need to
76219304Speter	 * be recovered.  The lock is already acquired, just copy it.
76319304Speter	 */
76419304Speter	ep = sp->ep;
76519304Speter	ep->rcv_mpath = recp;
76619304Speter	ep->rcv_fd = sv_fd;
76719304Speter	if (!locked)
76819304Speter		F_SET(frp, FR_UNLOCKED);
76919304Speter
77019304Speter	/* We believe the file is recoverable. */
77119304Speter	F_SET(ep, F_RCV_ON);
77219304Speter	return (0);
77319304Speter}
77419304Speter
77519304Speter/*
77619304Speter * rcv_copy --
77719304Speter *	Copy a recovery file.
77819304Speter */
77919304Speterstatic int
78019304Speterrcv_copy(sp, wfd, fname)
78119304Speter	SCR *sp;
78219304Speter	int wfd;
78319304Speter	char *fname;
78419304Speter{
78519304Speter	int nr, nw, off, rfd;
78619304Speter	char buf[8 * 1024];
78719304Speter
78819304Speter	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
78919304Speter		goto err;
79019304Speter	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
79119304Speter		for (off = 0; nr; nr -= nw, off += nw)
79219304Speter			if ((nw = write(wfd, buf + off, nr)) < 0)
79319304Speter				goto err;
79419304Speter	if (nr == 0)
79519304Speter		return (0);
79619304Speter
79719304Spetererr:	msgq_str(sp, M_SYSERR, fname, "%s");
79819304Speter	return (1);
79919304Speter}
80019304Speter
80119304Speter/*
80219304Speter * rcv_gets --
80319304Speter *	Fgets(3) for a file descriptor.
80419304Speter */
80519304Speterstatic char *
80619304Speterrcv_gets(buf, len, fd)
80719304Speter	char *buf;
80819304Speter	size_t len;
80919304Speter	int fd;
81019304Speter{
81119304Speter	int nr;
81219304Speter	char *p;
81319304Speter
81419304Speter	if ((nr = read(fd, buf, len - 1)) == -1)
81519304Speter		return (NULL);
81619304Speter	if ((p = strchr(buf, '\n')) == NULL)
81719304Speter		return (NULL);
81819304Speter	(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
81919304Speter	return (buf);
82019304Speter}
82119304Speter
82219304Speter/*
82319304Speter * rcv_mktemp --
82419304Speter *	Paranoid make temporary file routine.
82519304Speter */
82619304Speterstatic int
82719304Speterrcv_mktemp(sp, path, dname, perms)
82819304Speter	SCR *sp;
82919304Speter	char *path, *dname;
83019304Speter	int perms;
83119304Speter{
83219304Speter	int fd;
83319304Speter
83419304Speter	/*
83519304Speter	 * !!!
83619304Speter	 * We expect mkstemp(3) to set the permissions correctly.  On
83719304Speter	 * historic System V systems, mkstemp didn't.  Do it here, on
83819304Speter	 * GP's.
83919304Speter	 *
84019304Speter	 * XXX
84119304Speter	 * The variable perms should really be a mode_t, and it would
84219304Speter	 * be nice to use fchmod(2) instead of chmod(2), here.
84319304Speter	 */
84419304Speter	if ((fd = mkstemp(path)) == -1)
84519304Speter		msgq_str(sp, M_SYSERR, dname, "%s");
84619304Speter	else
84719304Speter		(void)chmod(path, perms);
84819304Speter	return (fd);
84919304Speter}
85019304Speter
85119304Speter/*
85219304Speter * rcv_email --
85319304Speter *	Send email.
85419304Speter */
85519304Speterstatic void
85619304Speterrcv_email(sp, fname)
85719304Speter	SCR *sp;
85819304Speter	char *fname;
85919304Speter{
86019304Speter	struct stat sb;
86119304Speter	char buf[MAXPATHLEN * 2 + 20];
86219304Speter
86319304Speter	if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
86419304Speter		msgq_str(sp, M_SYSERR,
86519304Speter		    _PATH_SENDMAIL, "071|not sending email: %s");
86619304Speter	else {
86719304Speter		/*
86819304Speter		 * !!!
86919304Speter		 * If you need to port this to a system that doesn't have
87019304Speter		 * sendmail, the -t flag causes sendmail to read the message
87119304Speter		 * for the recipients instead of specifying them some other
87219304Speter		 * way.
87319304Speter		 */
87419304Speter		(void)snprintf(buf, sizeof(buf),
87519304Speter		    "%s -t < %s", _PATH_SENDMAIL, fname);
87619304Speter		(void)system(buf);
87719304Speter	}
87819304Speter}
879