xref: /minix/external/bsd/nvi/dist/common/recover.c (revision 0a6a1f1d)
1*0a6a1f1dSLionel Sambuc /*	$NetBSD: recover.c,v 1.5 2014/01/26 21:43:45 christos Exp $ */
284d9c625SLionel Sambuc /*-
384d9c625SLionel Sambuc  * Copyright (c) 1993, 1994
484d9c625SLionel Sambuc  *	The Regents of the University of California.  All rights reserved.
584d9c625SLionel Sambuc  * Copyright (c) 1993, 1994, 1995, 1996
684d9c625SLionel Sambuc  *	Keith Bostic.  All rights reserved.
784d9c625SLionel Sambuc  *
884d9c625SLionel Sambuc  * See the LICENSE file for redistribution information.
984d9c625SLionel Sambuc  */
1084d9c625SLionel Sambuc 
1184d9c625SLionel Sambuc #include "config.h"
1284d9c625SLionel Sambuc 
13*0a6a1f1dSLionel Sambuc #include <sys/cdefs.h>
14*0a6a1f1dSLionel Sambuc #if 0
1584d9c625SLionel Sambuc #ifndef lint
1684d9c625SLionel Sambuc static const char sccsid[] = "Id: recover.c,v 10.31 2001/11/01 15:24:44 skimo Exp  (Berkeley) Date: 2001/11/01 15:24:44 ";
1784d9c625SLionel Sambuc #endif /* not lint */
18*0a6a1f1dSLionel Sambuc #else
19*0a6a1f1dSLionel Sambuc __RCSID("$NetBSD: recover.c,v 1.5 2014/01/26 21:43:45 christos Exp $");
20*0a6a1f1dSLionel Sambuc #endif
2184d9c625SLionel Sambuc 
2284d9c625SLionel Sambuc #include <sys/param.h>
2384d9c625SLionel Sambuc #include <sys/types.h>		/* XXX: param.h may not have included types.h */
2484d9c625SLionel Sambuc #include <sys/queue.h>
2584d9c625SLionel Sambuc #include <sys/stat.h>
2684d9c625SLionel Sambuc 
2784d9c625SLionel Sambuc /*
2884d9c625SLionel Sambuc  * We include <sys/file.h>, because the open #defines were found there
2984d9c625SLionel Sambuc  * on historical systems.  We also include <fcntl.h> because the open(2)
3084d9c625SLionel Sambuc  * #defines are found there on newer systems.
3184d9c625SLionel Sambuc  */
3284d9c625SLionel Sambuc #include <sys/file.h>
3384d9c625SLionel Sambuc 
3484d9c625SLionel Sambuc #include <bitstring.h>
3584d9c625SLionel Sambuc #include <dirent.h>
3684d9c625SLionel Sambuc #include <errno.h>
3784d9c625SLionel Sambuc #include <fcntl.h>
3884d9c625SLionel Sambuc #include <limits.h>
3984d9c625SLionel Sambuc #include <pwd.h>
4084d9c625SLionel Sambuc #include <stdio.h>
4184d9c625SLionel Sambuc #include <stdlib.h>
4284d9c625SLionel Sambuc #include <string.h>
4384d9c625SLionel Sambuc #include <time.h>
4484d9c625SLionel Sambuc #include <unistd.h>
4584d9c625SLionel Sambuc 
4684d9c625SLionel Sambuc #include "common.h"
4784d9c625SLionel Sambuc #include "pathnames.h"
4884d9c625SLionel Sambuc 
4984d9c625SLionel Sambuc /*
5084d9c625SLionel Sambuc  * Recovery code.
5184d9c625SLionel Sambuc  *
5284d9c625SLionel Sambuc  * The basic scheme is as follows.  In the EXF structure, we maintain full
5384d9c625SLionel Sambuc  * paths of a b+tree file and a mail recovery file.  The former is the file
5484d9c625SLionel Sambuc  * used as backing store by the DB package.  The latter is the file that
5584d9c625SLionel Sambuc  * contains an email message to be sent to the user if we crash.  The two
5684d9c625SLionel Sambuc  * simple states of recovery are:
5784d9c625SLionel Sambuc  *
5884d9c625SLionel Sambuc  *	+ first starting the edit session:
5984d9c625SLionel Sambuc  *		the b+tree file exists and is mode 700, the mail recovery
6084d9c625SLionel Sambuc  *		file doesn't exist.
6184d9c625SLionel Sambuc  *	+ after the file has been modified:
6284d9c625SLionel Sambuc  *		the b+tree file exists and is mode 600, the mail recovery
6384d9c625SLionel Sambuc  *		file exists, and is exclusively locked.
6484d9c625SLionel Sambuc  *
6584d9c625SLionel Sambuc  * In the EXF structure we maintain a file descriptor that is the locked
6684d9c625SLionel Sambuc  * file descriptor for the mail recovery file.  NOTE: we sometimes have to
6784d9c625SLionel Sambuc  * do locking with fcntl(2).  This is a problem because if you close(2) any
6884d9c625SLionel Sambuc  * file descriptor associated with the file, ALL of the locks go away.  Be
6984d9c625SLionel Sambuc  * sure to remember that if you have to modify the recovery code.  (It has
7084d9c625SLionel Sambuc  * been rhetorically asked of what the designers could have been thinking
7184d9c625SLionel Sambuc  * when they did that interface.  The answer is simple: they weren't.)
7284d9c625SLionel Sambuc  *
7384d9c625SLionel Sambuc  * To find out if a recovery file/backing file pair are in use, try to get
7484d9c625SLionel Sambuc  * a lock on the recovery file.
7584d9c625SLionel Sambuc  *
7684d9c625SLionel Sambuc  * To find out if a backing file can be deleted at boot time, check for an
7784d9c625SLionel Sambuc  * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
7884d9c625SLionel Sambuc  * special stuff into the backing file itself, or correlate the files at
7984d9c625SLionel Sambuc  * boot time, neither of which looks like fun.)  Note also that there's a
8084d9c625SLionel Sambuc  * window between when the file is created and the X bit is set.  It's small,
8184d9c625SLionel Sambuc  * but it's there.  To fix the window, check for 0 length files as well.
8284d9c625SLionel Sambuc  *
8384d9c625SLionel Sambuc  * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
8484d9c625SLionel Sambuc  * this DOES NOT mean that any initialization has been done, only that we
8584d9c625SLionel Sambuc  * haven't yet failed at setting up or doing recovery.
8684d9c625SLionel Sambuc  *
8784d9c625SLionel Sambuc  * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
8884d9c625SLionel Sambuc  * If that bit is not set when ending a file session:
8984d9c625SLionel Sambuc  *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
9084d9c625SLionel Sambuc  *	they are unlink(2)'d, and free(3)'d.
9184d9c625SLionel Sambuc  *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
9284d9c625SLionel Sambuc  *
9384d9c625SLionel Sambuc  * The backing b+tree file is set up when a file is first edited, so that
9484d9c625SLionel Sambuc  * the DB package can use it for on-disk caching and/or to snapshot the
9584d9c625SLionel Sambuc  * file.  When the file is first modified, the mail recovery file is created,
9684d9c625SLionel Sambuc  * the backing file permissions are updated, the file is sync(2)'d to disk,
9784d9c625SLionel Sambuc  * and the timer is started.  Then, at RCV_PERIOD second intervals, the
9884d9c625SLionel Sambuc  * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
9984d9c625SLionel Sambuc  * means that the data structures (SCR, EXF, the underlying tree structures)
10084d9c625SLionel Sambuc  * must be consistent when the signal arrives.
10184d9c625SLionel Sambuc  *
10284d9c625SLionel Sambuc  * The recovery mail file contains normal mail headers, with two additions,
10384d9c625SLionel Sambuc  * which occur in THIS order, as the FIRST TWO headers:
10484d9c625SLionel Sambuc  *
10584d9c625SLionel Sambuc  *	X-vi-recover-file: file_name
10684d9c625SLionel Sambuc  *	X-vi-recover-path: recover_path
10784d9c625SLionel Sambuc  *
10884d9c625SLionel Sambuc  * Since newlines delimit the headers, this means that file names cannot have
10984d9c625SLionel Sambuc  * newlines in them, but that's probably okay.  As these files aren't intended
11084d9c625SLionel Sambuc  * to be long-lived, changing their format won't be too painful.
11184d9c625SLionel Sambuc  *
11284d9c625SLionel Sambuc  * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
11384d9c625SLionel Sambuc  */
11484d9c625SLionel Sambuc 
11584d9c625SLionel Sambuc #define	VI_FHEADER	"X-vi-recover-file: "
11684d9c625SLionel Sambuc #define	VI_PHEADER	"X-vi-recover-path: "
11784d9c625SLionel Sambuc 
11884d9c625SLionel Sambuc static int	 rcv_copy __P((SCR *, int, char *));
11984d9c625SLionel Sambuc static void	 rcv_email __P((SCR *, char *));
12084d9c625SLionel Sambuc static char	*rcv_gets __P((char *, size_t, int));
12184d9c625SLionel Sambuc static int	 rcv_mailfile __P((SCR *, int, char *));
12284d9c625SLionel Sambuc static int	 rcv_mktemp __P((SCR *, char *, const char *, int));
12384d9c625SLionel Sambuc 
12484d9c625SLionel Sambuc /*
12584d9c625SLionel Sambuc  * rcv_tmp --
12684d9c625SLionel Sambuc  *	Build a file name that will be used as the recovery file.
12784d9c625SLionel Sambuc  *
12884d9c625SLionel Sambuc  * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
12984d9c625SLionel Sambuc  */
13084d9c625SLionel Sambuc int
rcv_tmp(SCR * sp,EXF * ep,char * name)13184d9c625SLionel Sambuc rcv_tmp(SCR *sp, EXF *ep, char *name)
13284d9c625SLionel Sambuc {
13384d9c625SLionel Sambuc 	struct stat sb;
13484d9c625SLionel Sambuc 	int fd;
13584d9c625SLionel Sambuc 	char path[MAXPATHLEN];
13684d9c625SLionel Sambuc 	const char *dp;
13784d9c625SLionel Sambuc 
13884d9c625SLionel Sambuc 	/*
13984d9c625SLionel Sambuc 	 * !!!
14084d9c625SLionel Sambuc 	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
14184d9c625SLionel Sambuc 	 *
14284d9c625SLionel Sambuc 	 *
14384d9c625SLionel Sambuc 	 * If the recovery directory doesn't exist, try and create it.  As
14484d9c625SLionel Sambuc 	 * the recovery files are themselves protected from reading/writing
14584d9c625SLionel Sambuc 	 * by other than the owner, the worst that can happen is that a user
14684d9c625SLionel Sambuc 	 * would have permission to remove other user's recovery files.  If
14784d9c625SLionel Sambuc 	 * the sticky bit has the BSD semantics, that too will be impossible.
14884d9c625SLionel Sambuc 	 */
14984d9c625SLionel Sambuc 	if (opts_empty(sp, O_RECDIR, 0))
15084d9c625SLionel Sambuc 		goto err;
15184d9c625SLionel Sambuc 	dp = O_STR(sp, O_RECDIR);
15284d9c625SLionel Sambuc 	if (stat(dp, &sb)) {
15384d9c625SLionel Sambuc 		if (errno != ENOENT || mkdir(dp, 0)) {
15484d9c625SLionel Sambuc 			msgq(sp, M_SYSERR, "%s", dp);
15584d9c625SLionel Sambuc 			goto err;
15684d9c625SLionel Sambuc 		}
15784d9c625SLionel Sambuc 		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
15884d9c625SLionel Sambuc 	}
15984d9c625SLionel Sambuc 
16084d9c625SLionel Sambuc 	/* Newlines delimit the mail messages. */
16184d9c625SLionel Sambuc 	if (strchr(name, '\n')) {
16284d9c625SLionel Sambuc 		msgq(sp, M_ERR,
16384d9c625SLionel Sambuc 		    "055|Files with newlines in the name are unrecoverable");
16484d9c625SLionel Sambuc 		goto err;
16584d9c625SLionel Sambuc 	}
16684d9c625SLionel Sambuc 
16784d9c625SLionel Sambuc 	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
16884d9c625SLionel Sambuc 	if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
16984d9c625SLionel Sambuc 		goto err;
17084d9c625SLionel Sambuc 	(void)close(fd);
17184d9c625SLionel Sambuc 
17284d9c625SLionel Sambuc 	if ((ep->rcv_path = strdup(path)) == NULL) {
17384d9c625SLionel Sambuc 		msgq(sp, M_SYSERR, NULL);
17484d9c625SLionel Sambuc 		(void)unlink(path);
17584d9c625SLionel Sambuc err:		msgq(sp, M_ERR,
17684d9c625SLionel Sambuc 		    "056|Modifications not recoverable if the session fails");
17784d9c625SLionel Sambuc 		return (1);
17884d9c625SLionel Sambuc 	}
17984d9c625SLionel Sambuc 
18084d9c625SLionel Sambuc 	/* We believe the file is recoverable. */
18184d9c625SLionel Sambuc 	F_SET(ep, F_RCV_ON);
18284d9c625SLionel Sambuc 	return (0);
18384d9c625SLionel Sambuc }
18484d9c625SLionel Sambuc 
18584d9c625SLionel Sambuc /*
18684d9c625SLionel Sambuc  * rcv_init --
18784d9c625SLionel Sambuc  *	Force the file to be snapshotted for recovery.
18884d9c625SLionel Sambuc  *
18984d9c625SLionel Sambuc  * PUBLIC: int rcv_init __P((SCR *));
19084d9c625SLionel Sambuc  */
19184d9c625SLionel Sambuc int
rcv_init(SCR * sp)19284d9c625SLionel Sambuc rcv_init(SCR *sp)
19384d9c625SLionel Sambuc {
19484d9c625SLionel Sambuc 	EXF *ep;
19584d9c625SLionel Sambuc 	db_recno_t lno;
19684d9c625SLionel Sambuc 
19784d9c625SLionel Sambuc 	ep = sp->ep;
19884d9c625SLionel Sambuc 
19984d9c625SLionel Sambuc 	/* Only do this once. */
20084d9c625SLionel Sambuc 	F_CLR(ep, F_FIRSTMODIFY);
20184d9c625SLionel Sambuc 
20284d9c625SLionel Sambuc 	/* If we already know the file isn't recoverable, we're done. */
20384d9c625SLionel Sambuc 	if (!F_ISSET(ep, F_RCV_ON))
20484d9c625SLionel Sambuc 		return (0);
20584d9c625SLionel Sambuc 
20684d9c625SLionel Sambuc 	/* Turn off recoverability until we figure out if this will work. */
20784d9c625SLionel Sambuc 	F_CLR(ep, F_RCV_ON);
20884d9c625SLionel Sambuc 
20984d9c625SLionel Sambuc 	/* Test if we're recovering a file, not editing one. */
21084d9c625SLionel Sambuc 	if (ep->rcv_mpath == NULL) {
21184d9c625SLionel Sambuc 		/* Build a file to mail to the user. */
21284d9c625SLionel Sambuc 		if (rcv_mailfile(sp, 0, NULL))
21384d9c625SLionel Sambuc 			goto err;
21484d9c625SLionel Sambuc 
21584d9c625SLionel Sambuc 		/* Force a read of the entire file. */
21684d9c625SLionel Sambuc 		if (db_last(sp, &lno))
21784d9c625SLionel Sambuc 			goto err;
21884d9c625SLionel Sambuc 
21984d9c625SLionel Sambuc 		/* Turn on a busy message, and sync it to backing store. */
22084d9c625SLionel Sambuc 		sp->gp->scr_busy(sp,
22184d9c625SLionel Sambuc 		    "057|Copying file for recovery...", BUSY_ON);
22284d9c625SLionel Sambuc 		if (ep->db->sync(ep->db, 0)) {
22384d9c625SLionel Sambuc 			msgq_str(sp, M_SYSERR, ep->rcv_path,
22484d9c625SLionel Sambuc 			    "058|Preservation failed: %s");
22584d9c625SLionel Sambuc 			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
22684d9c625SLionel Sambuc 			goto err;
22784d9c625SLionel Sambuc 		}
22884d9c625SLionel Sambuc 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
22984d9c625SLionel Sambuc 	}
23084d9c625SLionel Sambuc 
23184d9c625SLionel Sambuc 	/* Turn off the owner execute bit. */
23284d9c625SLionel Sambuc 	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
23384d9c625SLionel Sambuc 
23484d9c625SLionel Sambuc 	/* We believe the file is recoverable. */
23584d9c625SLionel Sambuc 	F_SET(ep, F_RCV_ON);
23684d9c625SLionel Sambuc 	return (0);
23784d9c625SLionel Sambuc 
23884d9c625SLionel Sambuc err:	msgq(sp, M_ERR,
23984d9c625SLionel Sambuc 	    "059|Modifications not recoverable if the session fails");
24084d9c625SLionel Sambuc 	return (1);
24184d9c625SLionel Sambuc }
24284d9c625SLionel Sambuc 
24384d9c625SLionel Sambuc /*
24484d9c625SLionel Sambuc  * rcv_sync --
24584d9c625SLionel Sambuc  *	Sync the file, optionally:
24684d9c625SLionel Sambuc  *		flagging the backup file to be preserved
24784d9c625SLionel Sambuc  *		snapshotting the backup file and send email to the user
24884d9c625SLionel Sambuc  *		sending email to the user if the file was modified
24984d9c625SLionel Sambuc  *		ending the file session
25084d9c625SLionel Sambuc  *
25184d9c625SLionel Sambuc  * PUBLIC: int rcv_sync __P((SCR *, u_int));
25284d9c625SLionel Sambuc  */
25384d9c625SLionel Sambuc int
rcv_sync(SCR * sp,u_int flags)25484d9c625SLionel Sambuc rcv_sync(SCR *sp, u_int flags)
25584d9c625SLionel Sambuc {
25684d9c625SLionel Sambuc 	EXF *ep;
25784d9c625SLionel Sambuc 	int fd, rval;
25884d9c625SLionel Sambuc 	char buf[1024];
25984d9c625SLionel Sambuc 	const char *dp;
26084d9c625SLionel Sambuc 
26184d9c625SLionel Sambuc 	/* Make sure that there's something to recover/sync. */
26284d9c625SLionel Sambuc 	ep = sp->ep;
26384d9c625SLionel Sambuc 	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
26484d9c625SLionel Sambuc 		return (0);
26584d9c625SLionel Sambuc 
26684d9c625SLionel Sambuc 	/* Sync the file if it's been modified. */
26784d9c625SLionel Sambuc 	if (F_ISSET(ep, F_MODIFIED)) {
26884d9c625SLionel Sambuc 		/*
26984d9c625SLionel Sambuc 		 * If we are using a db1 version of the database,
27084d9c625SLionel Sambuc 		 * we want to sync the underlying btree not the
27184d9c625SLionel Sambuc 		 * recno tree which is transient anyway.
27284d9c625SLionel Sambuc 		 */
27384d9c625SLionel Sambuc #ifndef R_RECNOSYNC
27484d9c625SLionel Sambuc #define	R_RECNOSYNC 0
27584d9c625SLionel Sambuc #endif
27684d9c625SLionel Sambuc 		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
27784d9c625SLionel Sambuc 			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
27884d9c625SLionel Sambuc 			msgq_str(sp, M_SYSERR,
27984d9c625SLionel Sambuc 			    ep->rcv_path, "060|File backup failed: %s");
28084d9c625SLionel Sambuc 			return (1);
28184d9c625SLionel Sambuc 		}
28284d9c625SLionel Sambuc 
28384d9c625SLionel Sambuc 		/* REQUEST: don't remove backing file on exit. */
28484d9c625SLionel Sambuc 		if (LF_ISSET(RCV_PRESERVE))
28584d9c625SLionel Sambuc 			F_SET(ep, F_RCV_NORM);
28684d9c625SLionel Sambuc 
28784d9c625SLionel Sambuc 		/* REQUEST: send email. */
28884d9c625SLionel Sambuc 		if (LF_ISSET(RCV_EMAIL))
28984d9c625SLionel Sambuc 			rcv_email(sp, ep->rcv_mpath);
29084d9c625SLionel Sambuc 	}
29184d9c625SLionel Sambuc 
29284d9c625SLionel Sambuc 	/*
29384d9c625SLionel Sambuc 	 * !!!
29484d9c625SLionel Sambuc 	 * Each time the user exec's :preserve, we have to snapshot all of
29584d9c625SLionel Sambuc 	 * the recovery information, i.e. it's like the user re-edited the
29684d9c625SLionel Sambuc 	 * file.  We copy the DB(3) backing file, and then create a new mail
29784d9c625SLionel Sambuc 	 * recovery file, it's simpler than exiting and reopening all of the
29884d9c625SLionel Sambuc 	 * underlying files.
29984d9c625SLionel Sambuc 	 *
30084d9c625SLionel Sambuc 	 * REQUEST: snapshot the file.
30184d9c625SLionel Sambuc 	 */
30284d9c625SLionel Sambuc 	rval = 0;
30384d9c625SLionel Sambuc 	if (LF_ISSET(RCV_SNAPSHOT)) {
30484d9c625SLionel Sambuc 		if (opts_empty(sp, O_RECDIR, 0))
30584d9c625SLionel Sambuc 			goto err;
30684d9c625SLionel Sambuc 		dp = O_STR(sp, O_RECDIR);
30784d9c625SLionel Sambuc 		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
30884d9c625SLionel Sambuc 		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
30984d9c625SLionel Sambuc 			goto err;
31084d9c625SLionel Sambuc 		sp->gp->scr_busy(sp,
31184d9c625SLionel Sambuc 		    "061|Copying file for recovery...", BUSY_ON);
31284d9c625SLionel Sambuc 		if (rcv_copy(sp, fd, ep->rcv_path) ||
31384d9c625SLionel Sambuc 		    close(fd) || rcv_mailfile(sp, 1, buf)) {
31484d9c625SLionel Sambuc 			(void)unlink(buf);
31584d9c625SLionel Sambuc 			(void)close(fd);
31684d9c625SLionel Sambuc 			rval = 1;
31784d9c625SLionel Sambuc 		}
31884d9c625SLionel Sambuc 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
31984d9c625SLionel Sambuc 	}
32084d9c625SLionel Sambuc 	if (0) {
32184d9c625SLionel Sambuc err:		rval = 1;
32284d9c625SLionel Sambuc 	}
32384d9c625SLionel Sambuc 
32484d9c625SLionel Sambuc 	/* REQUEST: end the file session. */
32584d9c625SLionel Sambuc 	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
32684d9c625SLionel Sambuc 		rval = 1;
32784d9c625SLionel Sambuc 
32884d9c625SLionel Sambuc 	return (rval);
32984d9c625SLionel Sambuc }
33084d9c625SLionel Sambuc 
33184d9c625SLionel Sambuc /*
33284d9c625SLionel Sambuc  * rcv_mailfile --
33384d9c625SLionel Sambuc  *	Build the file to mail to the user.
33484d9c625SLionel Sambuc  */
33584d9c625SLionel Sambuc static int
rcv_mailfile(SCR * sp,int issync,char * cp_path)33684d9c625SLionel Sambuc rcv_mailfile(SCR *sp, int issync, char *cp_path)
33784d9c625SLionel Sambuc {
33884d9c625SLionel Sambuc 	EXF *ep;
33984d9c625SLionel Sambuc 	GS *gp;
34084d9c625SLionel Sambuc 	struct passwd *pw;
34184d9c625SLionel Sambuc 	size_t len;
34284d9c625SLionel Sambuc 	time_t now;
34384d9c625SLionel Sambuc 	uid_t uid;
34484d9c625SLionel Sambuc 	int fd;
34584d9c625SLionel Sambuc 	char *p, *t, buf[4096], mpath[MAXPATHLEN];
34684d9c625SLionel Sambuc 	const char *dp;
34784d9c625SLionel Sambuc 	char *t1, *t2, *t3;
34884d9c625SLionel Sambuc 
34984d9c625SLionel Sambuc 	/*
35084d9c625SLionel Sambuc 	 * XXX
35184d9c625SLionel Sambuc 	 * MAXHOSTNAMELEN is in various places on various systems, including
35284d9c625SLionel Sambuc 	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
35384d9c625SLionel Sambuc 	 */
35484d9c625SLionel Sambuc #ifndef MAXHOSTNAMELEN
35584d9c625SLionel Sambuc #define	MAXHOSTNAMELEN	1024
35684d9c625SLionel Sambuc #endif
35784d9c625SLionel Sambuc 	char host[MAXHOSTNAMELEN];
35884d9c625SLionel Sambuc 
35984d9c625SLionel Sambuc 	gp = sp->gp;
36084d9c625SLionel Sambuc 	if ((pw = getpwuid(uid = getuid())) == NULL) {
36184d9c625SLionel Sambuc 		msgq(sp, M_ERR,
36284d9c625SLionel Sambuc 		    "062|Information on user id %u not found", uid);
36384d9c625SLionel Sambuc 		return (1);
36484d9c625SLionel Sambuc 	}
36584d9c625SLionel Sambuc 
36684d9c625SLionel Sambuc 	if (opts_empty(sp, O_RECDIR, 0))
36784d9c625SLionel Sambuc 		return (1);
36884d9c625SLionel Sambuc 	dp = O_STR(sp, O_RECDIR);
36984d9c625SLionel Sambuc 	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
37084d9c625SLionel Sambuc 	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
37184d9c625SLionel Sambuc 		return (1);
37284d9c625SLionel Sambuc 
37384d9c625SLionel Sambuc 	/*
37484d9c625SLionel Sambuc 	 * XXX
37584d9c625SLionel Sambuc 	 * We keep an open lock on the file so that the recover option can
37684d9c625SLionel Sambuc 	 * distinguish between files that are live and those that need to
37784d9c625SLionel Sambuc 	 * be recovered.  There's an obvious window between the mkstemp call
37884d9c625SLionel Sambuc 	 * and the lock, but it's pretty small.
37984d9c625SLionel Sambuc 	 */
38084d9c625SLionel Sambuc 	ep = sp->ep;
38184d9c625SLionel Sambuc 	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
38284d9c625SLionel Sambuc 		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
38384d9c625SLionel Sambuc 	if (!issync) {
38484d9c625SLionel Sambuc 		/* Save the recover file descriptor, and mail path. */
38584d9c625SLionel Sambuc 		ep->rcv_fd = fd;
38684d9c625SLionel Sambuc 		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
38784d9c625SLionel Sambuc 			msgq(sp, M_SYSERR, NULL);
38884d9c625SLionel Sambuc 			goto err;
38984d9c625SLionel Sambuc 		}
39084d9c625SLionel Sambuc 		cp_path = ep->rcv_path;
39184d9c625SLionel Sambuc 	}
39284d9c625SLionel Sambuc 
39384d9c625SLionel Sambuc 	/*
39484d9c625SLionel Sambuc 	 * XXX
39584d9c625SLionel Sambuc 	 * We can't use stdio(3) here.  The problem is that we may be using
39684d9c625SLionel Sambuc 	 * fcntl(2), so if ANY file descriptor into the file is closed, the
39784d9c625SLionel Sambuc 	 * lock is lost.  So, we could never close the FILE *, even if we
39884d9c625SLionel Sambuc 	 * dup'd the fd first.
39984d9c625SLionel Sambuc 	 */
40084d9c625SLionel Sambuc 	t = sp->frp->name;
40184d9c625SLionel Sambuc 	if ((p = strrchr(t, '/')) == NULL)
40284d9c625SLionel Sambuc 		p = t;
40384d9c625SLionel Sambuc 	else
40484d9c625SLionel Sambuc 		++p;
40584d9c625SLionel Sambuc 	(void)time(&now);
40684d9c625SLionel Sambuc 	(void)gethostname(host, sizeof(host));
40784d9c625SLionel Sambuc 	len = snprintf(buf, sizeof(buf),
40884d9c625SLionel Sambuc 	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
40984d9c625SLionel Sambuc 	    VI_FHEADER, t,			/* Non-standard. */
41084d9c625SLionel Sambuc 	    VI_PHEADER, cp_path,		/* Non-standard. */
41184d9c625SLionel Sambuc 	    "Reply-To: root",
41284d9c625SLionel Sambuc 	    "From: root (Nvi recovery program)",
41384d9c625SLionel Sambuc 	    "To: ", pw->pw_name,
41484d9c625SLionel Sambuc 	    "Subject: Nvi saved the file ", p,
41584d9c625SLionel Sambuc 	    "Precedence: bulk");		/* For vacation(1). */
41684d9c625SLionel Sambuc 	if (len > sizeof(buf) - 1)
41784d9c625SLionel Sambuc 		goto lerr;
41884d9c625SLionel Sambuc 	if ((size_t)write(fd, buf, len) != len)
41984d9c625SLionel Sambuc 		goto werr;
42084d9c625SLionel Sambuc 
42184d9c625SLionel Sambuc 	len = snprintf(buf, sizeof(buf),
42284d9c625SLionel Sambuc 	    "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
42384d9c625SLionel Sambuc 	    "On ", ctime(&now), ", the user ", pw->pw_name,
42484d9c625SLionel Sambuc 	    " was editing a file named ", t, " on the machine ",
42584d9c625SLionel Sambuc 	    host, ", when it was saved for recovery. ",
42684d9c625SLionel Sambuc 	    "You can recover most, if not all, of the changes ",
42784d9c625SLionel Sambuc 	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
42884d9c625SLionel Sambuc 	    gp->progname, " -r ", t);
42984d9c625SLionel Sambuc 	if (len > sizeof(buf) - 1) {
43084d9c625SLionel Sambuc lerr:		msgq(sp, M_ERR, "064|Recovery file buffer overrun");
43184d9c625SLionel Sambuc 		goto err;
43284d9c625SLionel Sambuc 	}
43384d9c625SLionel Sambuc 
43484d9c625SLionel Sambuc 	/*
43584d9c625SLionel Sambuc 	 * Format the message.  (Yes, I know it's silly.)
43684d9c625SLionel Sambuc 	 * Requires that the message end in a <newline>.
43784d9c625SLionel Sambuc 	 */
43884d9c625SLionel Sambuc #define	FMTCOLS	60
43984d9c625SLionel Sambuc 	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
44084d9c625SLionel Sambuc 		/* Check for a short length. */
44184d9c625SLionel Sambuc 		if (len <= FMTCOLS) {
44284d9c625SLionel Sambuc 			t2 = t1 + (len - 1);
44384d9c625SLionel Sambuc 			goto wout;
44484d9c625SLionel Sambuc 		}
44584d9c625SLionel Sambuc 
44684d9c625SLionel Sambuc 		/* Check for a required <newline>. */
44784d9c625SLionel Sambuc 		t2 = strchr(t1, '\n');
44884d9c625SLionel Sambuc 		if (t2 - t1 <= FMTCOLS)
44984d9c625SLionel Sambuc 			goto wout;
45084d9c625SLionel Sambuc 
45184d9c625SLionel Sambuc 		/* Find the closest space, if any. */
45284d9c625SLionel Sambuc 		for (t3 = t2; t2 > t1; --t2)
45384d9c625SLionel Sambuc 			if (*t2 == ' ') {
45484d9c625SLionel Sambuc 				if (t2 - t1 <= FMTCOLS)
45584d9c625SLionel Sambuc 					goto wout;
45684d9c625SLionel Sambuc 				t3 = t2;
45784d9c625SLionel Sambuc 			}
45884d9c625SLionel Sambuc 		t2 = t3;
45984d9c625SLionel Sambuc 
46084d9c625SLionel Sambuc 		/* t2 points to the last character to display. */
46184d9c625SLionel Sambuc wout:		*t2++ = '\n';
46284d9c625SLionel Sambuc 
46384d9c625SLionel Sambuc 		/* t2 points one after the last character to display. */
46484d9c625SLionel Sambuc 		if (write(fd, t1, t2 - t1) != t2 - t1)
46584d9c625SLionel Sambuc 			goto werr;
46684d9c625SLionel Sambuc 	}
46784d9c625SLionel Sambuc 
46884d9c625SLionel Sambuc 	if (issync) {
46984d9c625SLionel Sambuc 		rcv_email(sp, mpath);
47084d9c625SLionel Sambuc 		if (close(fd)) {
47184d9c625SLionel Sambuc werr:			msgq(sp, M_SYSERR, "065|Recovery file");
47284d9c625SLionel Sambuc 			goto err;
47384d9c625SLionel Sambuc 		}
47484d9c625SLionel Sambuc 	}
47584d9c625SLionel Sambuc 	return (0);
47684d9c625SLionel Sambuc 
47784d9c625SLionel Sambuc err:	if (!issync)
47884d9c625SLionel Sambuc 		ep->rcv_fd = -1;
47984d9c625SLionel Sambuc 	if (fd != -1)
48084d9c625SLionel Sambuc 		(void)close(fd);
48184d9c625SLionel Sambuc 	return (1);
48284d9c625SLionel Sambuc }
48384d9c625SLionel Sambuc 
48484d9c625SLionel Sambuc /*
48584d9c625SLionel Sambuc  *	people making love
48684d9c625SLionel Sambuc  *	never exactly the same
48784d9c625SLionel Sambuc  *	just like a snowflake
48884d9c625SLionel Sambuc  *
48984d9c625SLionel Sambuc  * rcv_list --
49084d9c625SLionel Sambuc  *	List the files that can be recovered by this user.
49184d9c625SLionel Sambuc  *
49284d9c625SLionel Sambuc  * PUBLIC: int rcv_list __P((SCR *));
49384d9c625SLionel Sambuc  */
49484d9c625SLionel Sambuc int
rcv_list(SCR * sp)49584d9c625SLionel Sambuc rcv_list(SCR *sp)
49684d9c625SLionel Sambuc {
49784d9c625SLionel Sambuc 	struct dirent *dp;
49884d9c625SLionel Sambuc 	struct stat sb;
49984d9c625SLionel Sambuc 	DIR *dirp;
50084d9c625SLionel Sambuc 	FILE *fp;
50184d9c625SLionel Sambuc 	int found;
50284d9c625SLionel Sambuc 	char *p, *t;
50384d9c625SLionel Sambuc 	const char *d;
50484d9c625SLionel Sambuc 	char file[MAXPATHLEN], path[MAXPATHLEN];
50584d9c625SLionel Sambuc 
50684d9c625SLionel Sambuc 	/* Open the recovery directory for reading. */
50784d9c625SLionel Sambuc 	if (opts_empty(sp, O_RECDIR, 0))
50884d9c625SLionel Sambuc 		return (1);
50984d9c625SLionel Sambuc 	d = O_STR(sp, O_RECDIR);
51084d9c625SLionel Sambuc 	if (chdir(d) || (dirp = opendir(".")) == NULL) {
51184d9c625SLionel Sambuc 		msgq_str(sp, M_SYSERR, d, "recdir: %s");
51284d9c625SLionel Sambuc 		return (1);
51384d9c625SLionel Sambuc 	}
51484d9c625SLionel Sambuc 
51584d9c625SLionel Sambuc 	/* Read the directory. */
51684d9c625SLionel Sambuc 	for (found = 0; (dp = readdir(dirp)) != NULL;) {
51784d9c625SLionel Sambuc 		if (strncmp(dp->d_name, "recover.", 8))
51884d9c625SLionel Sambuc 			continue;
51984d9c625SLionel Sambuc 
52084d9c625SLionel Sambuc 		/*
52184d9c625SLionel Sambuc 		 * If it's readable, it's recoverable.
52284d9c625SLionel Sambuc 		 *
52384d9c625SLionel Sambuc 		 * XXX
52484d9c625SLionel Sambuc 		 * Should be "r", we don't want to write the file.  However,
52584d9c625SLionel Sambuc 		 * if we're using fcntl(2), there's no way to lock a file
52684d9c625SLionel Sambuc 		 * descriptor that's not open for writing.
52784d9c625SLionel Sambuc 		 */
52884d9c625SLionel Sambuc 		if ((fp = fopen(dp->d_name, "r+")) == NULL)
52984d9c625SLionel Sambuc 			continue;
53084d9c625SLionel Sambuc 
53184d9c625SLionel Sambuc 		switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
53284d9c625SLionel Sambuc 		case LOCK_FAILED:
53384d9c625SLionel Sambuc 			/*
53484d9c625SLionel Sambuc 			 * XXX
53584d9c625SLionel Sambuc 			 * Assume that a lock can't be acquired, but that we
53684d9c625SLionel Sambuc 			 * should permit recovery anyway.  If this is wrong,
53784d9c625SLionel Sambuc 			 * and someone else is using the file, we're going to
53884d9c625SLionel Sambuc 			 * die horribly.
53984d9c625SLionel Sambuc 			 */
54084d9c625SLionel Sambuc 			break;
54184d9c625SLionel Sambuc 		case LOCK_SUCCESS:
54284d9c625SLionel Sambuc 			break;
54384d9c625SLionel Sambuc 		case LOCK_UNAVAIL:
54484d9c625SLionel Sambuc 			/* If it's locked, it's live. */
54584d9c625SLionel Sambuc 			(void)fclose(fp);
54684d9c625SLionel Sambuc 			continue;
54784d9c625SLionel Sambuc 		}
54884d9c625SLionel Sambuc 
54984d9c625SLionel Sambuc 		/* Check the headers. */
55084d9c625SLionel Sambuc 		if (fgets(file, sizeof(file), fp) == NULL ||
55184d9c625SLionel Sambuc 		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
55284d9c625SLionel Sambuc 		    (p = strchr(file, '\n')) == NULL ||
55384d9c625SLionel Sambuc 		    fgets(path, sizeof(path), fp) == NULL ||
55484d9c625SLionel Sambuc 		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
55584d9c625SLionel Sambuc 		    (t = strchr(path, '\n')) == NULL) {
55684d9c625SLionel Sambuc 			msgq_str(sp, M_ERR, dp->d_name,
55784d9c625SLionel Sambuc 			    "066|%s: malformed recovery file");
55884d9c625SLionel Sambuc 			goto next;
55984d9c625SLionel Sambuc 		}
56084d9c625SLionel Sambuc 		*p = *t = '\0';
56184d9c625SLionel Sambuc 
56284d9c625SLionel Sambuc 		/*
56384d9c625SLionel Sambuc 		 * If the file doesn't exist, it's an orphaned recovery file,
56484d9c625SLionel Sambuc 		 * toss it.
56584d9c625SLionel Sambuc 		 *
56684d9c625SLionel Sambuc 		 * XXX
56784d9c625SLionel Sambuc 		 * This can occur if the backup file was deleted and we crashed
56884d9c625SLionel Sambuc 		 * before deleting the email file.
56984d9c625SLionel Sambuc 		 */
57084d9c625SLionel Sambuc 		errno = 0;
57184d9c625SLionel Sambuc 		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
57284d9c625SLionel Sambuc 		    errno == ENOENT) {
57384d9c625SLionel Sambuc 			(void)unlink(dp->d_name);
57484d9c625SLionel Sambuc 			goto next;
57584d9c625SLionel Sambuc 		}
57684d9c625SLionel Sambuc 
57784d9c625SLionel Sambuc 		/* Get the last modification time and display. */
57884d9c625SLionel Sambuc 		(void)fstat(fileno(fp), &sb);
57984d9c625SLionel Sambuc 		(void)printf("%.24s: %s\n",
58084d9c625SLionel Sambuc 		    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
58184d9c625SLionel Sambuc 		found = 1;
58284d9c625SLionel Sambuc 
58384d9c625SLionel Sambuc 		/* Close, discarding lock. */
58484d9c625SLionel Sambuc next:		(void)fclose(fp);
58584d9c625SLionel Sambuc 	}
58684d9c625SLionel Sambuc 	if (found == 0)
58784d9c625SLionel Sambuc 		(void)printf("%s: No files to recover\n", sp->gp->progname);
58884d9c625SLionel Sambuc 	(void)closedir(dirp);
58984d9c625SLionel Sambuc 	return (0);
59084d9c625SLionel Sambuc }
59184d9c625SLionel Sambuc 
59284d9c625SLionel Sambuc /*
59384d9c625SLionel Sambuc  * rcv_read --
59484d9c625SLionel Sambuc  *	Start a recovered file as the file to edit.
59584d9c625SLionel Sambuc  *
59684d9c625SLionel Sambuc  * PUBLIC: int rcv_read __P((SCR *, FREF *));
59784d9c625SLionel Sambuc  */
59884d9c625SLionel Sambuc int
rcv_read(SCR * sp,FREF * frp)59984d9c625SLionel Sambuc rcv_read(SCR *sp, FREF *frp)
60084d9c625SLionel Sambuc {
60184d9c625SLionel Sambuc 	struct dirent *dp;
60284d9c625SLionel Sambuc 	struct stat sb;
60384d9c625SLionel Sambuc 	DIR *dirp;
60484d9c625SLionel Sambuc 	EXF *ep;
60584d9c625SLionel Sambuc 	time_t rec_mtime;
60684d9c625SLionel Sambuc 	int fd, found, locked = 0, requested, sv_fd;
60784d9c625SLionel Sambuc 	char *name, *p, *t, *recp, *pathp;
60884d9c625SLionel Sambuc 	const char *rp;
60984d9c625SLionel Sambuc 	char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
61084d9c625SLionel Sambuc 
61184d9c625SLionel Sambuc 	if (opts_empty(sp, O_RECDIR, 0))
61284d9c625SLionel Sambuc 		return (1);
61384d9c625SLionel Sambuc 	rp = O_STR(sp, O_RECDIR);
61484d9c625SLionel Sambuc 	if ((dirp = opendir(rp)) == NULL) {
61584d9c625SLionel Sambuc 		msgq_str(sp, M_ERR, rp, "%s");
61684d9c625SLionel Sambuc 		return (1);
61784d9c625SLionel Sambuc 	}
61884d9c625SLionel Sambuc 
61984d9c625SLionel Sambuc 	name = frp->name;
62084d9c625SLionel Sambuc 	sv_fd = -1;
62184d9c625SLionel Sambuc 	rec_mtime = 0;
62284d9c625SLionel Sambuc 	recp = pathp = NULL;
62384d9c625SLionel Sambuc 	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
62484d9c625SLionel Sambuc 		if (strncmp(dp->d_name, "recover.", 8))
62584d9c625SLionel Sambuc 			continue;
62684d9c625SLionel Sambuc 		(void)snprintf(recpath,
62784d9c625SLionel Sambuc 		    sizeof(recpath), "%s/%s", rp, dp->d_name);
62884d9c625SLionel Sambuc 
62984d9c625SLionel Sambuc 		/*
63084d9c625SLionel Sambuc 		 * If it's readable, it's recoverable.  It would be very
63184d9c625SLionel Sambuc 		 * nice to use stdio(3), but, we can't because that would
63284d9c625SLionel Sambuc 		 * require closing and then reopening the file so that we
63384d9c625SLionel Sambuc 		 * could have a lock and still close the FP.  Another tip
63484d9c625SLionel Sambuc 		 * of the hat to fcntl(2).
63584d9c625SLionel Sambuc 		 *
63684d9c625SLionel Sambuc 		 * XXX
63784d9c625SLionel Sambuc 		 * Should be O_RDONLY, we don't want to write it.  However,
63884d9c625SLionel Sambuc 		 * if we're using fcntl(2), there's no way to lock a file
63984d9c625SLionel Sambuc 		 * descriptor that's not open for writing.
64084d9c625SLionel Sambuc 		 */
64184d9c625SLionel Sambuc 		if ((fd = open(recpath, O_RDWR, 0)) == -1)
64284d9c625SLionel Sambuc 			continue;
64384d9c625SLionel Sambuc 
64484d9c625SLionel Sambuc 		switch (file_lock(sp, NULL, NULL, fd, 1)) {
64584d9c625SLionel Sambuc 		case LOCK_FAILED:
64684d9c625SLionel Sambuc 			/*
64784d9c625SLionel Sambuc 			 * XXX
64884d9c625SLionel Sambuc 			 * Assume that a lock can't be acquired, but that we
64984d9c625SLionel Sambuc 			 * should permit recovery anyway.  If this is wrong,
65084d9c625SLionel Sambuc 			 * and someone else is using the file, we're going to
65184d9c625SLionel Sambuc 			 * die horribly.
65284d9c625SLionel Sambuc 			 */
65384d9c625SLionel Sambuc 			locked = 0;
65484d9c625SLionel Sambuc 			break;
65584d9c625SLionel Sambuc 		case LOCK_SUCCESS:
65684d9c625SLionel Sambuc 			locked = 1;
65784d9c625SLionel Sambuc 			break;
65884d9c625SLionel Sambuc 		case LOCK_UNAVAIL:
65984d9c625SLionel Sambuc 			/* If it's locked, it's live. */
66084d9c625SLionel Sambuc 			(void)close(fd);
66184d9c625SLionel Sambuc 			continue;
66284d9c625SLionel Sambuc 		}
66384d9c625SLionel Sambuc 
66484d9c625SLionel Sambuc 		/* Check the headers. */
66584d9c625SLionel Sambuc 		if (rcv_gets(file, sizeof(file), fd) == NULL ||
66684d9c625SLionel Sambuc 		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
66784d9c625SLionel Sambuc 		    (p = strchr(file, '\n')) == NULL ||
66884d9c625SLionel Sambuc 		    rcv_gets(path, sizeof(path), fd) == NULL ||
66984d9c625SLionel Sambuc 		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
67084d9c625SLionel Sambuc 		    (t = strchr(path, '\n')) == NULL) {
67184d9c625SLionel Sambuc 			msgq_str(sp, M_ERR, recpath,
67284d9c625SLionel Sambuc 			    "067|%s: malformed recovery file");
67384d9c625SLionel Sambuc 			goto next;
67484d9c625SLionel Sambuc 		}
67584d9c625SLionel Sambuc 		*p = *t = '\0';
67684d9c625SLionel Sambuc 		++found;
67784d9c625SLionel Sambuc 
67884d9c625SLionel Sambuc 		/*
67984d9c625SLionel Sambuc 		 * If the file doesn't exist, it's an orphaned recovery file,
68084d9c625SLionel Sambuc 		 * toss it.
68184d9c625SLionel Sambuc 		 *
68284d9c625SLionel Sambuc 		 * XXX
68384d9c625SLionel Sambuc 		 * This can occur if the backup file was deleted and we crashed
68484d9c625SLionel Sambuc 		 * before deleting the email file.
68584d9c625SLionel Sambuc 		 */
68684d9c625SLionel Sambuc 		errno = 0;
68784d9c625SLionel Sambuc 		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
68884d9c625SLionel Sambuc 		    errno == ENOENT) {
68984d9c625SLionel Sambuc 			(void)unlink(dp->d_name);
69084d9c625SLionel Sambuc 			goto next;
69184d9c625SLionel Sambuc 		}
69284d9c625SLionel Sambuc 
69384d9c625SLionel Sambuc 		/* Check the file name. */
69484d9c625SLionel Sambuc 		if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
69584d9c625SLionel Sambuc 			goto next;
69684d9c625SLionel Sambuc 
69784d9c625SLionel Sambuc 		++requested;
69884d9c625SLionel Sambuc 
69984d9c625SLionel Sambuc 		/*
70084d9c625SLionel Sambuc 		 * If we've found more than one, take the most recent.
70184d9c625SLionel Sambuc 		 *
70284d9c625SLionel Sambuc 		 * XXX
70384d9c625SLionel Sambuc 		 * Since we're using st_mtime, for portability reasons,
70484d9c625SLionel Sambuc 		 * we only get a single second granularity, instead of
70584d9c625SLionel Sambuc 		 * getting it right.
70684d9c625SLionel Sambuc 		 */
70784d9c625SLionel Sambuc 		(void)fstat(fd, &sb);
70884d9c625SLionel Sambuc 		if (recp == NULL || rec_mtime < sb.st_mtime) {
70984d9c625SLionel Sambuc 			p = recp;
71084d9c625SLionel Sambuc 			t = pathp;
71184d9c625SLionel Sambuc 			if ((recp = strdup(recpath)) == NULL) {
71284d9c625SLionel Sambuc 				msgq(sp, M_SYSERR, NULL);
71384d9c625SLionel Sambuc 				recp = p;
71484d9c625SLionel Sambuc 				goto next;
71584d9c625SLionel Sambuc 			}
71684d9c625SLionel Sambuc 			if ((pathp = strdup(path)) == NULL) {
71784d9c625SLionel Sambuc 				msgq(sp, M_SYSERR, NULL);
71884d9c625SLionel Sambuc 				free(recp);
71984d9c625SLionel Sambuc 				recp = p;
72084d9c625SLionel Sambuc 				pathp = t;
72184d9c625SLionel Sambuc 				goto next;
72284d9c625SLionel Sambuc 			}
72384d9c625SLionel Sambuc 			if (p != NULL) {
72484d9c625SLionel Sambuc 				free(p);
72584d9c625SLionel Sambuc 				free(t);
72684d9c625SLionel Sambuc 			}
72784d9c625SLionel Sambuc 			rec_mtime = sb.st_mtime;
72884d9c625SLionel Sambuc 			if (sv_fd != -1)
72984d9c625SLionel Sambuc 				(void)close(sv_fd);
73084d9c625SLionel Sambuc 			sv_fd = fd;
73184d9c625SLionel Sambuc 		} else
73284d9c625SLionel Sambuc next:			(void)close(fd);
73384d9c625SLionel Sambuc 	}
73484d9c625SLionel Sambuc 	(void)closedir(dirp);
73584d9c625SLionel Sambuc 
73684d9c625SLionel Sambuc 	if (recp == NULL) {
73784d9c625SLionel Sambuc 		msgq_str(sp, M_INFO, name,
73884d9c625SLionel Sambuc 		    "068|No files named %s, readable by you, to recover");
73984d9c625SLionel Sambuc 		return (1);
74084d9c625SLionel Sambuc 	}
74184d9c625SLionel Sambuc 	if (found) {
74284d9c625SLionel Sambuc 		if (requested > 1)
74384d9c625SLionel Sambuc 			msgq(sp, M_INFO,
74484d9c625SLionel Sambuc 	    "069|There are older versions of this file for you to recover");
74584d9c625SLionel Sambuc 		if (found > requested)
74684d9c625SLionel Sambuc 			msgq(sp, M_INFO,
74784d9c625SLionel Sambuc 			    "070|There are other files for you to recover");
74884d9c625SLionel Sambuc 	}
74984d9c625SLionel Sambuc 
75084d9c625SLionel Sambuc 	/*
75184d9c625SLionel Sambuc 	 * Create the FREF structure, start the btree file.
75284d9c625SLionel Sambuc 	 *
75384d9c625SLionel Sambuc 	 * XXX
75484d9c625SLionel Sambuc 	 * file_init() is going to set ep->rcv_path.
75584d9c625SLionel Sambuc 	 */
75684d9c625SLionel Sambuc 	if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
75784d9c625SLionel Sambuc 		free(recp);
75884d9c625SLionel Sambuc 		free(pathp);
75984d9c625SLionel Sambuc 		(void)close(sv_fd);
76084d9c625SLionel Sambuc 		return (1);
76184d9c625SLionel Sambuc 	}
76284d9c625SLionel Sambuc 
76384d9c625SLionel Sambuc 	/*
76484d9c625SLionel Sambuc 	 * We keep an open lock on the file so that the recover option can
76584d9c625SLionel Sambuc 	 * distinguish between files that are live and those that need to
76684d9c625SLionel Sambuc 	 * be recovered.  The lock is already acquired, just copy it.
76784d9c625SLionel Sambuc 	 */
76884d9c625SLionel Sambuc 	ep = sp->ep;
76984d9c625SLionel Sambuc 	ep->rcv_mpath = recp;
77084d9c625SLionel Sambuc 	ep->rcv_fd = sv_fd;
77184d9c625SLionel Sambuc 	if (!locked)
77284d9c625SLionel Sambuc 		F_SET(frp, FR_UNLOCKED);
77384d9c625SLionel Sambuc 
77484d9c625SLionel Sambuc 	/* We believe the file is recoverable. */
77584d9c625SLionel Sambuc 	F_SET(ep, F_RCV_ON);
77684d9c625SLionel Sambuc 	free(pathp);
77784d9c625SLionel Sambuc 	return (0);
77884d9c625SLionel Sambuc }
77984d9c625SLionel Sambuc 
78084d9c625SLionel Sambuc /*
78184d9c625SLionel Sambuc  * rcv_copy --
78284d9c625SLionel Sambuc  *	Copy a recovery file.
78384d9c625SLionel Sambuc  */
78484d9c625SLionel Sambuc static int
rcv_copy(SCR * sp,int wfd,char * fname)78584d9c625SLionel Sambuc rcv_copy(SCR *sp, int wfd, char *fname)
78684d9c625SLionel Sambuc {
78784d9c625SLionel Sambuc 	int nr, nw, off, rfd;
78884d9c625SLionel Sambuc 	char buf[8 * 1024];
78984d9c625SLionel Sambuc 
79084d9c625SLionel Sambuc 	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
79184d9c625SLionel Sambuc 		goto err;
79284d9c625SLionel Sambuc 	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
79384d9c625SLionel Sambuc 		for (off = 0; nr; nr -= nw, off += nw)
79484d9c625SLionel Sambuc 			if ((nw = write(wfd, buf + off, nr)) < 0)
79584d9c625SLionel Sambuc 				goto err;
79684d9c625SLionel Sambuc 	if (nr == 0)
79784d9c625SLionel Sambuc 		return (0);
79884d9c625SLionel Sambuc 
79984d9c625SLionel Sambuc err:	msgq_str(sp, M_SYSERR, fname, "%s");
80084d9c625SLionel Sambuc 	return (1);
80184d9c625SLionel Sambuc }
80284d9c625SLionel Sambuc 
80384d9c625SLionel Sambuc /*
80484d9c625SLionel Sambuc  * rcv_gets --
80584d9c625SLionel Sambuc  *	Fgets(3) for a file descriptor.
80684d9c625SLionel Sambuc  */
80784d9c625SLionel Sambuc static char *
rcv_gets(char * buf,size_t len,int fd)80884d9c625SLionel Sambuc rcv_gets(char *buf, size_t len, int fd)
80984d9c625SLionel Sambuc {
81084d9c625SLionel Sambuc 	int nr;
81184d9c625SLionel Sambuc 	char *p;
81284d9c625SLionel Sambuc 
81384d9c625SLionel Sambuc 	if ((nr = read(fd, buf, len - 1)) == -1)
81484d9c625SLionel Sambuc 		return (NULL);
81584d9c625SLionel Sambuc 	if ((p = strchr(buf, '\n')) == NULL)
81684d9c625SLionel Sambuc 		return (NULL);
81784d9c625SLionel Sambuc 	(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
81884d9c625SLionel Sambuc 	return (buf);
81984d9c625SLionel Sambuc }
82084d9c625SLionel Sambuc 
82184d9c625SLionel Sambuc /*
82284d9c625SLionel Sambuc  * rcv_mktemp --
82384d9c625SLionel Sambuc  *	Paranoid make temporary file routine.
82484d9c625SLionel Sambuc  */
82584d9c625SLionel Sambuc static int
rcv_mktemp(SCR * sp,char * path,const char * dname,int perms)82684d9c625SLionel Sambuc rcv_mktemp(SCR *sp, char *path, const char *dname, int perms)
82784d9c625SLionel Sambuc {
82884d9c625SLionel Sambuc 	int fd;
82984d9c625SLionel Sambuc 
83084d9c625SLionel Sambuc 	/*
83184d9c625SLionel Sambuc 	 * !!!
83284d9c625SLionel Sambuc 	 * We expect mkstemp(3) to set the permissions correctly.  On
83384d9c625SLionel Sambuc 	 * historic System V systems, mkstemp didn't.  Do it here, on
83484d9c625SLionel Sambuc 	 * GP's.
83584d9c625SLionel Sambuc 	 *
83684d9c625SLionel Sambuc 	 * XXX
83784d9c625SLionel Sambuc 	 * The variable perms should really be a mode_t, and it would
83884d9c625SLionel Sambuc 	 * be nice to use fchmod(2) instead of chmod(2), here.
83984d9c625SLionel Sambuc 	 */
84084d9c625SLionel Sambuc 	if ((fd = mkstemp(path)) == -1)
84184d9c625SLionel Sambuc 		msgq_str(sp, M_SYSERR, dname, "%s");
84284d9c625SLionel Sambuc 	else
84384d9c625SLionel Sambuc 		(void)chmod(path, perms);
84484d9c625SLionel Sambuc 	return (fd);
84584d9c625SLionel Sambuc }
84684d9c625SLionel Sambuc 
84784d9c625SLionel Sambuc /*
84884d9c625SLionel Sambuc  * rcv_email --
84984d9c625SLionel Sambuc  *	Send email.
85084d9c625SLionel Sambuc  */
85184d9c625SLionel Sambuc static void
rcv_email(SCR * sp,char * fname)85284d9c625SLionel Sambuc rcv_email(SCR *sp, char *fname)
85384d9c625SLionel Sambuc {
85484d9c625SLionel Sambuc 	struct stat sb;
85584d9c625SLionel Sambuc 	char buf[MAXPATHLEN * 2 + 20];
85684d9c625SLionel Sambuc 
85784d9c625SLionel Sambuc 	if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
85884d9c625SLionel Sambuc 		msgq_str(sp, M_SYSERR,
85984d9c625SLionel Sambuc 		    _PATH_SENDMAIL, "071|not sending email: %s");
86084d9c625SLionel Sambuc 	else {
86184d9c625SLionel Sambuc 		/*
86284d9c625SLionel Sambuc 		 * !!!
86384d9c625SLionel Sambuc 		 * If you need to port this to a system that doesn't have
86484d9c625SLionel Sambuc 		 * sendmail, the -t flag causes sendmail to read the message
86584d9c625SLionel Sambuc 		 * for the recipients instead of specifying them some other
86684d9c625SLionel Sambuc 		 * way.
86784d9c625SLionel Sambuc 		 */
86884d9c625SLionel Sambuc 		(void)snprintf(buf, sizeof(buf),
86984d9c625SLionel Sambuc 		    "%s -t < %s", _PATH_SENDMAIL, fname);
87084d9c625SLionel Sambuc 		(void)system(buf);
87184d9c625SLionel Sambuc 	}
87284d9c625SLionel Sambuc }
873