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