1/*	$NetBSD: recover.c,v 1.11 2017/11/10 20:01:11 christos Exp $ */
2/*-
3 * Copyright (c) 1993, 1994
4 *	The Regents of the University of California.  All rights reserved.
5 * Copyright (c) 1993, 1994, 1995, 1996
6 *	Keith Bostic.  All rights reserved.
7 *
8 * See the LICENSE file for redistribution information.
9 */
10
11#include "config.h"
12
13#include <sys/cdefs.h>
14#if 0
15#ifndef lint
16static 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 ";
17#endif /* not lint */
18#else
19__RCSID("$NetBSD: recover.c,v 1.11 2017/11/10 20:01:11 christos Exp $");
20#endif
21
22#include <sys/param.h>
23#include <sys/types.h>		/* XXX: param.h may not have included types.h */
24#include <sys/queue.h>
25#include <sys/wait.h>
26#include <sys/stat.h>
27
28/*
29 * We include <sys/file.h>, because the open #defines were found there
30 * on historical systems.  We also include <fcntl.h> because the open(2)
31 * #defines are found there on newer systems.
32 */
33#include <sys/file.h>
34
35#include <bitstring.h>
36#include <dirent.h>
37#include <errno.h>
38#include <fcntl.h>
39#include <limits.h>
40#include <pwd.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <time.h>
45#include <unistd.h>
46
47#include "common.h"
48#include "pathnames.h"
49
50/*
51 * Recovery code.
52 *
53 * The basic scheme is as follows.  In the EXF structure, we maintain full
54 * paths of a b+tree file and a mail recovery file.  The former is the file
55 * used as backing store by the DB package.  The latter is the file that
56 * contains an email message to be sent to the user if we crash.  The two
57 * simple states of recovery are:
58 *
59 *	+ first starting the edit session:
60 *		the b+tree file exists and is mode 700, the mail recovery
61 *		file doesn't exist.
62 *	+ after the file has been modified:
63 *		the b+tree file exists and is mode 600, the mail recovery
64 *		file exists, and is exclusively locked.
65 *
66 * In the EXF structure we maintain a file descriptor that is the locked
67 * file descriptor for the mail recovery file.  NOTE: we sometimes have to
68 * do locking with fcntl(2).  This is a problem because if you close(2) any
69 * file descriptor associated with the file, ALL of the locks go away.  Be
70 * sure to remember that if you have to modify the recovery code.  (It has
71 * been rhetorically asked of what the designers could have been thinking
72 * when they did that interface.  The answer is simple: they weren't.)
73 *
74 * To find out if a recovery file/backing file pair are in use, try to get
75 * a lock on the recovery file.
76 *
77 * To find out if a backing file can be deleted at boot time, check for an
78 * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
79 * special stuff into the backing file itself, or correlate the files at
80 * boot time, neither of which looks like fun.)  Note also that there's a
81 * window between when the file is created and the X bit is set.  It's small,
82 * but it's there.  To fix the window, check for 0 length files as well.
83 *
84 * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
85 * this DOES NOT mean that any initialization has been done, only that we
86 * haven't yet failed at setting up or doing recovery.
87 *
88 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
89 * If that bit is not set when ending a file session:
90 *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
91 *	they are unlink(2)'d, and free(3)'d.
92 *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
93 *
94 * The backing b+tree file is set up when a file is first edited, so that
95 * the DB package can use it for on-disk caching and/or to snapshot the
96 * file.  When the file is first modified, the mail recovery file is created,
97 * the backing file permissions are updated, the file is sync(2)'d to disk,
98 * and the timer is started.  Then, at RCV_PERIOD second intervals, the
99 * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
100 * means that the data structures (SCR, EXF, the underlying tree structures)
101 * must be consistent when the signal arrives.
102 *
103 * The recovery mail file contains normal mail headers, with two additions,
104 * which occur in THIS order, as the FIRST TWO headers:
105 *
106 *	X-vi-recover-file: file_name
107 *	X-vi-recover-path: recover_path
108 *
109 * Since newlines delimit the headers, this means that file names cannot have
110 * newlines in them, but that's probably okay.  As these files aren't intended
111 * to be long-lived, changing their format won't be too painful.
112 *
113 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
114 */
115
116#define	VI_FHEADER	"X-vi-recover-file: "
117#define	VI_PHEADER	"X-vi-recover-path: "
118
119static int	 rcv_copy(SCR *, int, char *);
120static void	 rcv_email(SCR *, int fd);
121static char	*rcv_gets(char *, size_t, int);
122static int	 rcv_mailfile(SCR *, int, char *);
123static int	 rcv_mktemp(SCR *, char *, const char *, int);
124
125#ifndef O_REGULAR
126#define O_REGULAR O_NONBLOCK
127#endif
128
129/*
130 * rcv_tmp --
131 *	Build a file name that will be used as the recovery file.
132 *
133 * PUBLIC: int rcv_tmp(SCR *, EXF *, char *);
134 */
135int
136rcv_tmp(SCR *sp, EXF *ep, char *name)
137{
138	struct stat sb;
139	int fd;
140	char path[MAXPATHLEN];
141	const char *dp;
142
143	/*
144	 * !!!
145	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
146	 *
147	 *
148	 * If the recovery directory doesn't exist, try and create it.  As
149	 * the recovery files are themselves protected from reading/writing
150	 * by other than the owner, the worst that can happen is that a user
151	 * would have permission to remove other user's recovery files.  If
152	 * the sticky bit has the BSD semantics, that too will be impossible.
153	 */
154	if (opts_empty(sp, O_RECDIR, 0))
155		goto err;
156	dp = O_STR(sp, O_RECDIR);
157	if (stat(dp, &sb)) {
158		if (errno != ENOENT || mkdir(dp, 0)) {
159			msgq(sp, M_SYSERR, "%s", dp);
160			goto err;
161		}
162		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
163	}
164
165	/* Newlines delimit the mail messages. */
166	if (strchr(name, '\n')) {
167		msgq(sp, M_ERR,
168		    "055|Files with newlines in the name are unrecoverable");
169		goto err;
170	}
171
172	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
173	if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
174		goto err;
175	(void)close(fd);
176
177	if ((ep->rcv_path = strdup(path)) == NULL) {
178		msgq(sp, M_SYSERR, NULL);
179		(void)unlink(path);
180err:		msgq(sp, M_ERR,
181		    "056|Modifications not recoverable if the session fails");
182		return (1);
183	}
184
185	/* We believe the file is recoverable. */
186	F_SET(ep, F_RCV_ON);
187	return (0);
188}
189
190/*
191 * rcv_init --
192 *	Force the file to be snapshotted for recovery.
193 *
194 * PUBLIC: int rcv_init(SCR *);
195 */
196int
197rcv_init(SCR *sp)
198{
199	EXF *ep;
200	db_recno_t lno;
201
202	ep = sp->ep;
203
204	/* Only do this once. */
205	F_CLR(ep, F_FIRSTMODIFY);
206
207	/* If we already know the file isn't recoverable, we're done. */
208	if (!F_ISSET(ep, F_RCV_ON))
209		return (0);
210
211	/* Turn off recoverability until we figure out if this will work. */
212	F_CLR(ep, F_RCV_ON);
213
214	/* Test if we're recovering a file, not editing one. */
215	if (ep->rcv_mpath == NULL) {
216		/* Build a file to mail to the user. */
217		if (rcv_mailfile(sp, 0, NULL))
218			goto err;
219
220		/* Force a read of the entire file. */
221		if (db_last(sp, &lno))
222			goto err;
223
224		/* Turn on a busy message, and sync it to backing store. */
225		sp->gp->scr_busy(sp,
226		    "057|Copying file for recovery...", BUSY_ON);
227		if (ep->db->sync(ep->db, 0)) {
228			msgq_str(sp, M_SYSERR, ep->rcv_path,
229			    "058|Preservation failed: %s");
230			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
231			goto err;
232		}
233		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
234	}
235
236	/* Turn off the owner execute bit. */
237	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
238
239	/* We believe the file is recoverable. */
240	F_SET(ep, F_RCV_ON);
241	return (0);
242
243err:	msgq(sp, M_ERR,
244	    "059|Modifications not recoverable if the session fails");
245	return (1);
246}
247
248/*
249 * rcv_sync --
250 *	Sync the file, optionally:
251 *		flagging the backup file to be preserved
252 *		snapshotting the backup file and send email to the user
253 *		sending email to the user if the file was modified
254 *		ending the file session
255 *
256 * PUBLIC: int rcv_sync(SCR *, u_int);
257 */
258int
259rcv_sync(SCR *sp, u_int flags)
260{
261	EXF *ep;
262	int fd, rval;
263	char buf[1024];
264	const char *dp;
265
266	/* Make sure that there's something to recover/sync. */
267	ep = sp->ep;
268	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
269		return (0);
270
271	/* Sync the file if it's been modified. */
272	if (F_ISSET(ep, F_MODIFIED)) {
273		/*
274		 * If we are using a db1 version of the database,
275		 * we want to sync the underlying btree not the
276		 * recno tree which is transient anyway.
277		 */
278#ifndef R_RECNOSYNC
279#define	R_RECNOSYNC 0
280#endif
281		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
282			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
283			msgq_str(sp, M_SYSERR,
284			    ep->rcv_path, "060|File backup failed: %s");
285			return (1);
286		}
287
288		/* REQUEST: don't remove backing file on exit. */
289		if (LF_ISSET(RCV_PRESERVE))
290			F_SET(ep, F_RCV_NORM);
291
292		/* REQUEST: send email. */
293		if (LF_ISSET(RCV_EMAIL))
294			rcv_email(sp, ep->rcv_fd);
295	}
296
297	/*
298	 * !!!
299	 * Each time the user exec's :preserve, we have to snapshot all of
300	 * the recovery information, i.e. it's like the user re-edited the
301	 * file.  We copy the DB(3) backing file, and then create a new mail
302	 * recovery file, it's simpler than exiting and reopening all of the
303	 * underlying files.
304	 *
305	 * REQUEST: snapshot the file.
306	 */
307	rval = 0;
308	if (LF_ISSET(RCV_SNAPSHOT)) {
309		if (opts_empty(sp, O_RECDIR, 0))
310			goto err;
311		dp = O_STR(sp, O_RECDIR);
312		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
313		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
314			goto err;
315		sp->gp->scr_busy(sp,
316		    "061|Copying file for recovery...", BUSY_ON);
317		if (rcv_copy(sp, fd, ep->rcv_path) ||
318		    close(fd) || rcv_mailfile(sp, 1, buf)) {
319			(void)unlink(buf);
320			(void)close(fd);
321			rval = 1;
322		}
323		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
324	}
325	if (0) {
326err:		rval = 1;
327	}
328
329	/* REQUEST: end the file session. */
330	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
331		rval = 1;
332
333	return (rval);
334}
335
336/*
337 * rcv_mailfile --
338 *	Build the file to mail to the user.
339 */
340static int
341rcv_mailfile(SCR *sp, int issync, char *cp_path)
342{
343	EXF *ep;
344	GS *gp;
345	struct passwd *pw;
346	size_t len;
347	time_t now;
348	uid_t uid;
349	int fd;
350	char *p, *t, buf[4096], mpath[MAXPATHLEN];
351	const char *dp;
352	char *t1, *t2, *t3;
353
354	/*
355	 * XXX
356	 * MAXHOSTNAMELEN is in various places on various systems, including
357	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
358	 */
359#ifndef MAXHOSTNAMELEN
360#define	MAXHOSTNAMELEN	1024
361#endif
362	char host[MAXHOSTNAMELEN];
363
364	gp = sp->gp;
365	if ((pw = getpwuid(uid = getuid())) == NULL) {
366		msgq(sp, M_ERR,
367		    "062|Information on user id %u not found", uid);
368		return (1);
369	}
370
371	if (opts_empty(sp, O_RECDIR, 0))
372		return (1);
373	dp = O_STR(sp, O_RECDIR);
374	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
375	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
376		return (1);
377
378	/*
379	 * XXX
380	 * We keep an open lock on the file so that the recover option can
381	 * distinguish between files that are live and those that need to
382	 * be recovered.  There's an obvious window between the mkstemp call
383	 * and the lock, but it's pretty small.
384	 */
385	ep = sp->ep;
386	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
387		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
388	if (!issync) {
389		/* Save the recover file descriptor, and mail path. */
390		ep->rcv_fd = fd;
391		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
392			msgq(sp, M_SYSERR, NULL);
393			goto err;
394		}
395		cp_path = ep->rcv_path;
396	}
397
398	/*
399	 * XXX
400	 * We can't use stdio(3) here.  The problem is that we may be using
401	 * fcntl(2), so if ANY file descriptor into the file is closed, the
402	 * lock is lost.  So, we could never close the FILE *, even if we
403	 * dup'd the fd first.
404	 */
405	t = sp->frp->name;
406	if ((p = strrchr(t, '/')) == NULL)
407		p = t;
408	else
409		++p;
410	(void)time(&now);
411	(void)gethostname(host, sizeof(host));
412	len = snprintf(buf, sizeof(buf),
413	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
414	    VI_FHEADER, t,			/* Non-standard. */
415	    VI_PHEADER, cp_path,		/* Non-standard. */
416	    "Reply-To: root",
417	    "From: root (Nvi recovery program)",
418	    "To: ", pw->pw_name,
419	    "Subject: Nvi saved the file ", p,
420	    "Precedence: bulk");		/* For vacation(1). */
421	if (len > sizeof(buf) - 1)
422		goto lerr;
423	if ((size_t)write(fd, buf, len) != len)
424		goto werr;
425
426	len = snprintf(buf, sizeof(buf),
427	    "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
428	    "On ", ctime(&now), ", the user ", pw->pw_name,
429	    " was editing a file named ", t, " on the machine ",
430	    host, ", when it was saved for recovery. ",
431	    "You can recover most, if not all, of the changes ",
432	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
433	    gp->progname, " -r ", t);
434	if (len > sizeof(buf) - 1) {
435lerr:		msgq(sp, M_ERR, "064|Recovery file buffer overrun");
436		goto err;
437	}
438
439	/*
440	 * Format the message.  (Yes, I know it's silly.)
441	 * Requires that the message end in a <newline>.
442	 */
443#define	FMTCOLS	60
444	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
445		/* Check for a short length. */
446		if (len <= FMTCOLS) {
447			t2 = t1 + (len - 1);
448			goto wout;
449		}
450
451		/* Check for a required <newline>. */
452		t2 = strchr(t1, '\n');
453		if (t2 - t1 <= FMTCOLS)
454			goto wout;
455
456		/* Find the closest space, if any. */
457		for (t3 = t2; t2 > t1; --t2)
458			if (*t2 == ' ') {
459				if (t2 - t1 <= FMTCOLS)
460					goto wout;
461				t3 = t2;
462			}
463		t2 = t3;
464
465		/* t2 points to the last character to display. */
466wout:		*t2++ = '\n';
467
468		/* t2 points one after the last character to display. */
469		if (write(fd, t1, t2 - t1) != t2 - t1)
470			goto werr;
471	}
472
473	if (issync) {
474		rcv_email(sp, fd);
475		if (close(fd)) {
476werr:			msgq(sp, M_SYSERR, "065|Recovery file");
477			goto err;
478		}
479	}
480	return (0);
481
482err:	if (!issync)
483		ep->rcv_fd = -1;
484	if (fd != -1)
485		(void)close(fd);
486	return (1);
487}
488
489/*
490 * Since vi creates recovery files only accessible by the user, files
491 * accessible by group or others are probably malicious so avoid them.
492 * This is simpler than checking for getuid() == st.st_uid and we want
493 * to preserve the functionality that root can recover anything which
494 * means that root should know better and be careful.
495 *
496 * Checking the mode is racy though (someone can chmod between the
497 * open and the stat call, so also check for uid match or root.
498 */
499static int
500checkok(int fd)
501{
502	struct stat sb;
503	uid_t uid = getuid();
504
505	return fstat(fd, &sb) != -1 && S_ISREG(sb.st_mode) &&
506	    (sb.st_mode & (S_IRWXG|S_IRWXO)) == 0 &&
507	    (uid == 0 || uid == sb.st_uid);
508}
509
510/*
511 *	people making love
512 *	never exactly the same
513 *	just like a snowflake
514 *
515 * rcv_list --
516 *	List the files that can be recovered by this user.
517 *
518 * PUBLIC: int rcv_list(SCR *);
519 */
520int
521rcv_list(SCR *sp)
522{
523	struct dirent *dp;
524	struct stat sb;
525	DIR *dirp;
526	FILE *fp;
527	int found;
528	char *p, *t;
529	const char *d;
530	char file[MAXPATHLEN], path[MAXPATHLEN];
531
532	/* Open the recovery directory for reading. */
533	if (opts_empty(sp, O_RECDIR, 0))
534		return (1);
535	d = O_STR(sp, O_RECDIR);
536	if (chdir(d) || (dirp = opendir(".")) == NULL) {
537		msgq_str(sp, M_SYSERR, d, "recdir: %s");
538		return (1);
539	}
540
541	/* Read the directory. */
542	for (found = 0; (dp = readdir(dirp)) != NULL;) {
543		if (strncmp(dp->d_name, "recover.", 8))
544			continue;
545
546		/*
547		 * If it's readable, it's recoverable.
548		 *
549		 * XXX
550		 * Should be "r", we don't want to write the file.  However,
551		 * if we're using fcntl(2), there's no way to lock a file
552		 * descriptor that's not open for writing.
553		 */
554		if ((fp = fopen(dp->d_name, "r+efl")) == NULL)
555			continue;
556
557		if (!checkok(fileno(fp))) {
558			(void)fclose(fp);
559			continue;
560		}
561
562		switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
563		case LOCK_FAILED:
564			/*
565			 * XXX
566			 * Assume that a lock can't be acquired, but that we
567			 * should permit recovery anyway.  If this is wrong,
568			 * and someone else is using the file, we're going to
569			 * die horribly.
570			 */
571			break;
572		case LOCK_SUCCESS:
573			break;
574		case LOCK_UNAVAIL:
575			/* If it's locked, it's live. */
576			(void)fclose(fp);
577			continue;
578		}
579
580		/* Check the headers. */
581		if (fgets(file, sizeof(file), fp) == NULL ||
582		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
583		    (p = strchr(file, '\n')) == NULL ||
584		    fgets(path, sizeof(path), fp) == NULL ||
585		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
586		    (t = strchr(path, '\n')) == NULL) {
587			msgq_str(sp, M_ERR, dp->d_name,
588			    "066|%s: malformed recovery file");
589			goto next;
590		}
591		*p = *t = '\0';
592
593		/*
594		 * If the file doesn't exist, it's an orphaned recovery file,
595		 * toss it.
596		 *
597		 * XXX
598		 * This can occur if the backup file was deleted and we crashed
599		 * before deleting the email file.
600		 */
601		errno = 0;
602		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
603		    errno == ENOENT) {
604			(void)unlink(dp->d_name);
605			goto next;
606		}
607
608		/* Get the last modification time and display. */
609		(void)fstat(fileno(fp), &sb);
610		(void)printf("%.24s: %s\n",
611		    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
612		found = 1;
613
614		/* Close, discarding lock. */
615next:		(void)fclose(fp);
616	}
617	if (found == 0)
618		(void)printf("%s: No files to recover\n", sp->gp->progname);
619	(void)closedir(dirp);
620	return (0);
621}
622
623/*
624 * rcv_read --
625 *	Start a recovered file as the file to edit.
626 *
627 * PUBLIC: int rcv_read(SCR *, FREF *);
628 */
629int
630rcv_read(SCR *sp, FREF *frp)
631{
632	struct dirent *dp;
633	struct stat sb;
634	DIR *dirp;
635	EXF *ep;
636	time_t rec_mtime;
637	int fd, found, locked = 0, requested, sv_fd;
638	char *name, *p, *t, *recp, *pathp;
639	const char *rp;
640	char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
641
642	if (opts_empty(sp, O_RECDIR, 0))
643		return (1);
644	rp = O_STR(sp, O_RECDIR);
645	if ((dirp = opendir(rp)) == NULL) {
646		msgq_str(sp, M_ERR, rp, "%s");
647		return (1);
648	}
649
650	name = frp->name;
651	sv_fd = -1;
652	rec_mtime = 0;
653	recp = pathp = NULL;
654	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
655		if (strncmp(dp->d_name, "recover.", 8))
656			continue;
657		(void)snprintf(recpath,
658		    sizeof(recpath), "%s/%s", rp, dp->d_name);
659
660		/*
661		 * If it's readable, it's recoverable.  It would be very
662		 * nice to use stdio(3), but, we can't because that would
663		 * require closing and then reopening the file so that we
664		 * could have a lock and still close the FP.  Another tip
665		 * of the hat to fcntl(2).
666		 *
667		 * XXX
668		 * Should be O_RDONLY, we don't want to write it.  However,
669		 * if we're using fcntl(2), there's no way to lock a file
670		 * descriptor that's not open for writing.
671		 */
672		if ((fd = open(recpath, O_RDWR|O_REGULAR|O_NOFOLLOW|O_CLOEXEC,
673		    0)) == -1)
674			continue;
675
676		if (!checkok(fd)) {
677			(void)close(fd);
678			continue;
679		}
680
681		switch (file_lock(sp, NULL, NULL, fd, 1)) {
682		case LOCK_FAILED:
683			/*
684			 * XXX
685			 * Assume that a lock can't be acquired, but that we
686			 * should permit recovery anyway.  If this is wrong,
687			 * and someone else is using the file, we're going to
688			 * die horribly.
689			 */
690			locked = 0;
691			break;
692		case LOCK_SUCCESS:
693			locked = 1;
694			break;
695		case LOCK_UNAVAIL:
696			/* If it's locked, it's live. */
697			(void)close(fd);
698			continue;
699		}
700
701		/* Check the headers. */
702		if (rcv_gets(file, sizeof(file), fd) == NULL ||
703		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
704		    (p = strchr(file, '\n')) == NULL ||
705		    rcv_gets(path, sizeof(path), fd) == NULL ||
706		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
707		    (t = strchr(path, '\n')) == NULL) {
708			msgq_str(sp, M_ERR, recpath,
709			    "067|%s: malformed recovery file");
710			goto next;
711		}
712		*p = *t = '\0';
713		++found;
714
715		/*
716		 * If the file doesn't exist, it's an orphaned recovery file,
717		 * toss it.
718		 *
719		 * XXX
720		 * This can occur if the backup file was deleted and we crashed
721		 * before deleting the email file.
722		 */
723		errno = 0;
724		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
725		    errno == ENOENT) {
726			(void)unlink(dp->d_name);
727			goto next;
728		}
729
730		/* Check the file name. */
731		if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
732			goto next;
733
734		++requested;
735
736		/*
737		 * If we've found more than one, take the most recent.
738		 *
739		 * XXX
740		 * Since we're using st_mtime, for portability reasons,
741		 * we only get a single second granularity, instead of
742		 * getting it right.
743		 */
744		(void)fstat(fd, &sb);
745		if (recp == NULL || rec_mtime < sb.st_mtime) {
746			p = recp;
747			t = pathp;
748			if ((recp = strdup(recpath)) == NULL) {
749				msgq(sp, M_SYSERR, NULL);
750				recp = p;
751				goto next;
752			}
753			if ((pathp = strdup(path)) == NULL) {
754				msgq(sp, M_SYSERR, NULL);
755				free(recp);
756				recp = p;
757				pathp = t;
758				goto next;
759			}
760			if (p != NULL) {
761				free(p);
762				free(t);
763			}
764			rec_mtime = sb.st_mtime;
765			if (sv_fd != -1)
766				(void)close(sv_fd);
767			sv_fd = fd;
768		} else
769next:			(void)close(fd);
770	}
771	(void)closedir(dirp);
772
773	if (recp == NULL) {
774		msgq_str(sp, M_INFO, name,
775		    "068|No files named %s, readable by you, to recover");
776		return (1);
777	}
778	if (found) {
779		if (requested > 1)
780			msgq(sp, M_INFO,
781	    "069|There are older versions of this file for you to recover");
782		if (found > requested)
783			msgq(sp, M_INFO,
784			    "070|There are other files for you to recover");
785	}
786
787	/*
788	 * Create the FREF structure, start the btree file.
789	 *
790	 * XXX
791	 * file_init() is going to set ep->rcv_path.
792	 */
793	if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
794		free(recp);
795		free(pathp);
796		(void)close(sv_fd);
797		return (1);
798	}
799
800	/*
801	 * We keep an open lock on the file so that the recover option can
802	 * distinguish between files that are live and those that need to
803	 * be recovered.  The lock is already acquired, just copy it.
804	 */
805	ep = sp->ep;
806	ep->rcv_mpath = recp;
807	ep->rcv_fd = sv_fd;
808	if (!locked)
809		F_SET(frp, FR_UNLOCKED);
810
811	/* We believe the file is recoverable. */
812	F_SET(ep, F_RCV_ON);
813	free(pathp);
814	return (0);
815}
816
817/*
818 * rcv_copy --
819 *	Copy a recovery file.
820 */
821static int
822rcv_copy(SCR *sp, int wfd, char *fname)
823{
824	int nr, nw, off, rfd;
825	char buf[8 * 1024];
826
827	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
828		goto err;
829	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
830		for (off = 0; nr; nr -= nw, off += nw)
831			if ((nw = write(wfd, buf + off, nr)) < 0)
832				goto err;
833	if (nr == 0)
834		return (0);
835
836err:	msgq_str(sp, M_SYSERR, fname, "%s");
837	return (1);
838}
839
840/*
841 * rcv_gets --
842 *	Fgets(3) for a file descriptor.
843 */
844static char *
845rcv_gets(char *buf, size_t len, int fd)
846{
847	int nr;
848	char *p;
849
850	if ((nr = read(fd, buf, len - 1)) == -1)
851		return (NULL);
852	if ((p = strchr(buf, '\n')) == NULL)
853		return (NULL);
854	(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
855	return (buf);
856}
857
858/*
859 * rcv_mktemp --
860 *	Paranoid make temporary file routine.
861 */
862static int
863rcv_mktemp(SCR *sp, char *path, const char *dname, int perms)
864{
865	int fd;
866
867	/*
868	 * !!!
869	 * We expect mkstemp(3) to set the permissions correctly.  On
870	 * historic System V systems, mkstemp didn't.  Do it here, on
871	 * GP's.
872	 *
873	 * XXX
874	 * The variable perms should really be a mode_t, and it would
875	 * be nice to use fchmod(2) instead of chmod(2), here.
876	 */
877	if ((fd = mkstemp(path)) == -1)
878		msgq_str(sp, M_SYSERR, dname, "%s");
879	else
880		(void)chmod(path, perms);
881	return (fd);
882}
883
884/*
885 * rcv_email --
886 *	Send email.
887 */
888static void
889rcv_email(SCR *sp, int fd)
890{
891	struct stat sb;
892	pid_t pid;
893
894	if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb) == -1) {
895		msgq_str(sp, M_SYSERR,
896		    _PATH_SENDMAIL, "071|not sending email: %s");
897		return;
898	}
899
900	/*
901	 * !!!
902	 * If you need to port this to a system that doesn't have
903	 * sendmail, the -t flag causes sendmail to read the message
904	 * for the recipients instead of specifying them some other
905	 * way.
906	 */
907	switch (pid = fork()) {
908	case -1:                /* Error. */
909		msgq(sp, M_SYSERR, "fork");
910		break;
911	case 0:                 /* Sendmail. */
912		if (lseek(fd, 0, SEEK_SET) == -1) {
913			msgq(sp, M_SYSERR, "lseek");
914			_exit(127);
915		}
916		if (fd != STDIN_FILENO) {
917			(void)dup2(fd, STDIN_FILENO);
918			(void)close(fd);
919		}
920		execl(_PATH_SENDMAIL, "sendmail", "-t", NULL);
921		msgq(sp, M_SYSERR, _PATH_SENDMAIL);
922		_exit(127);
923	default:                /* Parent. */
924		while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
925			continue;
926		break;
927	}
928
929}
930