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