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