xref: /minix/external/bsd/nvi/dist/common/recover.c (revision 84d9c625)
1 /*	$NetBSD: recover.c,v 1.4 2013/11/30 14:54:29 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 #ifndef lint
14 static const char sccsid[] = "Id: recover.c,v 10.31 2001/11/01 15:24:44 skimo Exp  (Berkeley) Date: 2001/11/01 15:24:44 ";
15 #endif /* not lint */
16 
17 #include <sys/param.h>
18 #include <sys/types.h>		/* XXX: param.h may not have included types.h */
19 #include <sys/queue.h>
20 #include <sys/stat.h>
21 
22 /*
23  * We include <sys/file.h>, because the open #defines were found there
24  * on historical systems.  We also include <fcntl.h> because the open(2)
25  * #defines are found there on newer systems.
26  */
27 #include <sys/file.h>
28 
29 #include <bitstring.h>
30 #include <dirent.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <limits.h>
34 #include <pwd.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39 #include <unistd.h>
40 
41 #include "common.h"
42 #include "pathnames.h"
43 
44 /*
45  * Recovery code.
46  *
47  * The basic scheme is as follows.  In the EXF structure, we maintain full
48  * paths of a b+tree file and a mail recovery file.  The former is the file
49  * used as backing store by the DB package.  The latter is the file that
50  * contains an email message to be sent to the user if we crash.  The two
51  * simple states of recovery are:
52  *
53  *	+ first starting the edit session:
54  *		the b+tree file exists and is mode 700, the mail recovery
55  *		file doesn't exist.
56  *	+ after the file has been modified:
57  *		the b+tree file exists and is mode 600, the mail recovery
58  *		file exists, and is exclusively locked.
59  *
60  * In the EXF structure we maintain a file descriptor that is the locked
61  * file descriptor for the mail recovery file.  NOTE: we sometimes have to
62  * do locking with fcntl(2).  This is a problem because if you close(2) any
63  * file descriptor associated with the file, ALL of the locks go away.  Be
64  * sure to remember that if you have to modify the recovery code.  (It has
65  * been rhetorically asked of what the designers could have been thinking
66  * when they did that interface.  The answer is simple: they weren't.)
67  *
68  * To find out if a recovery file/backing file pair are in use, try to get
69  * a lock on the recovery file.
70  *
71  * To find out if a backing file can be deleted at boot time, check for an
72  * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
73  * special stuff into the backing file itself, or correlate the files at
74  * boot time, neither of which looks like fun.)  Note also that there's a
75  * window between when the file is created and the X bit is set.  It's small,
76  * but it's there.  To fix the window, check for 0 length files as well.
77  *
78  * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
79  * this DOES NOT mean that any initialization has been done, only that we
80  * haven't yet failed at setting up or doing recovery.
81  *
82  * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
83  * If that bit is not set when ending a file session:
84  *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
85  *	they are unlink(2)'d, and free(3)'d.
86  *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
87  *
88  * The backing b+tree file is set up when a file is first edited, so that
89  * the DB package can use it for on-disk caching and/or to snapshot the
90  * file.  When the file is first modified, the mail recovery file is created,
91  * the backing file permissions are updated, the file is sync(2)'d to disk,
92  * and the timer is started.  Then, at RCV_PERIOD second intervals, the
93  * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
94  * means that the data structures (SCR, EXF, the underlying tree structures)
95  * must be consistent when the signal arrives.
96  *
97  * The recovery mail file contains normal mail headers, with two additions,
98  * which occur in THIS order, as the FIRST TWO headers:
99  *
100  *	X-vi-recover-file: file_name
101  *	X-vi-recover-path: recover_path
102  *
103  * Since newlines delimit the headers, this means that file names cannot have
104  * newlines in them, but that's probably okay.  As these files aren't intended
105  * to be long-lived, changing their format won't be too painful.
106  *
107  * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
108  */
109 
110 #define	VI_FHEADER	"X-vi-recover-file: "
111 #define	VI_PHEADER	"X-vi-recover-path: "
112 
113 static int	 rcv_copy __P((SCR *, int, char *));
114 static void	 rcv_email __P((SCR *, char *));
115 static char	*rcv_gets __P((char *, size_t, int));
116 static int	 rcv_mailfile __P((SCR *, int, char *));
117 static int	 rcv_mktemp __P((SCR *, char *, const char *, int));
118 
119 /*
120  * rcv_tmp --
121  *	Build a file name that will be used as the recovery file.
122  *
123  * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
124  */
125 int
126 rcv_tmp(SCR *sp, EXF *ep, char *name)
127 {
128 	struct stat sb;
129 	int fd;
130 	char path[MAXPATHLEN];
131 	const char *dp;
132 
133 	/*
134 	 * !!!
135 	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
136 	 *
137 	 *
138 	 * If the recovery directory doesn't exist, try and create it.  As
139 	 * the recovery files are themselves protected from reading/writing
140 	 * by other than the owner, the worst that can happen is that a user
141 	 * would have permission to remove other user's recovery files.  If
142 	 * the sticky bit has the BSD semantics, that too will be impossible.
143 	 */
144 	if (opts_empty(sp, O_RECDIR, 0))
145 		goto err;
146 	dp = O_STR(sp, O_RECDIR);
147 	if (stat(dp, &sb)) {
148 		if (errno != ENOENT || mkdir(dp, 0)) {
149 			msgq(sp, M_SYSERR, "%s", dp);
150 			goto err;
151 		}
152 		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
153 	}
154 
155 	/* Newlines delimit the mail messages. */
156 	if (strchr(name, '\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.XXXXXX", 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 __P((SCR *));
185  */
186 int
187 rcv_init(SCR *sp)
188 {
189 	EXF *ep;
190 	db_recno_t lno;
191 
192 	ep = sp->ep;
193 
194 	/* Only do this once. */
195 	F_CLR(ep, F_FIRSTMODIFY);
196 
197 	/* If we already know the file isn't recoverable, we're done. */
198 	if (!F_ISSET(ep, F_RCV_ON))
199 		return (0);
200 
201 	/* Turn off recoverability until we figure out if this will work. */
202 	F_CLR(ep, F_RCV_ON);
203 
204 	/* Test if we're recovering a file, not editing one. */
205 	if (ep->rcv_mpath == NULL) {
206 		/* Build a file to mail to the user. */
207 		if (rcv_mailfile(sp, 0, NULL))
208 			goto err;
209 
210 		/* Force a read of the entire file. */
211 		if (db_last(sp, &lno))
212 			goto err;
213 
214 		/* Turn on a busy message, and sync it to backing store. */
215 		sp->gp->scr_busy(sp,
216 		    "057|Copying file for recovery...", BUSY_ON);
217 		if (ep->db->sync(ep->db, 0)) {
218 			msgq_str(sp, M_SYSERR, ep->rcv_path,
219 			    "058|Preservation failed: %s");
220 			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
221 			goto err;
222 		}
223 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
224 	}
225 
226 	/* Turn off the owner execute bit. */
227 	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
228 
229 	/* We believe the file is recoverable. */
230 	F_SET(ep, F_RCV_ON);
231 	return (0);
232 
233 err:	msgq(sp, M_ERR,
234 	    "059|Modifications not recoverable if the session fails");
235 	return (1);
236 }
237 
238 /*
239  * rcv_sync --
240  *	Sync the file, optionally:
241  *		flagging the backup file to be preserved
242  *		snapshotting the backup file and send email to the user
243  *		sending email to the user if the file was modified
244  *		ending the file session
245  *
246  * PUBLIC: int rcv_sync __P((SCR *, u_int));
247  */
248 int
249 rcv_sync(SCR *sp, u_int flags)
250 {
251 	EXF *ep;
252 	int fd, rval;
253 	char buf[1024];
254 	const char *dp;
255 
256 	/* Make sure that there's something to recover/sync. */
257 	ep = sp->ep;
258 	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
259 		return (0);
260 
261 	/* Sync the file if it's been modified. */
262 	if (F_ISSET(ep, F_MODIFIED)) {
263 		/*
264 		 * If we are using a db1 version of the database,
265 		 * we want to sync the underlying btree not the
266 		 * recno tree which is transient anyway.
267 		 */
268 #ifndef R_RECNOSYNC
269 #define	R_RECNOSYNC 0
270 #endif
271 		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
272 			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
273 			msgq_str(sp, M_SYSERR,
274 			    ep->rcv_path, "060|File backup failed: %s");
275 			return (1);
276 		}
277 
278 		/* REQUEST: don't remove backing file on exit. */
279 		if (LF_ISSET(RCV_PRESERVE))
280 			F_SET(ep, F_RCV_NORM);
281 
282 		/* REQUEST: send email. */
283 		if (LF_ISSET(RCV_EMAIL))
284 			rcv_email(sp, ep->rcv_mpath);
285 	}
286 
287 	/*
288 	 * !!!
289 	 * Each time the user exec's :preserve, we have to snapshot all of
290 	 * the recovery information, i.e. it's like the user re-edited the
291 	 * file.  We copy the DB(3) backing file, and then create a new mail
292 	 * recovery file, it's simpler than exiting and reopening all of the
293 	 * underlying files.
294 	 *
295 	 * REQUEST: snapshot the file.
296 	 */
297 	rval = 0;
298 	if (LF_ISSET(RCV_SNAPSHOT)) {
299 		if (opts_empty(sp, O_RECDIR, 0))
300 			goto err;
301 		dp = O_STR(sp, O_RECDIR);
302 		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
303 		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
304 			goto err;
305 		sp->gp->scr_busy(sp,
306 		    "061|Copying file for recovery...", BUSY_ON);
307 		if (rcv_copy(sp, fd, ep->rcv_path) ||
308 		    close(fd) || rcv_mailfile(sp, 1, buf)) {
309 			(void)unlink(buf);
310 			(void)close(fd);
311 			rval = 1;
312 		}
313 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
314 	}
315 	if (0) {
316 err:		rval = 1;
317 	}
318 
319 	/* REQUEST: end the file session. */
320 	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
321 		rval = 1;
322 
323 	return (rval);
324 }
325 
326 /*
327  * rcv_mailfile --
328  *	Build the file to mail to the user.
329  */
330 static int
331 rcv_mailfile(SCR *sp, int issync, 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 *p, *t, buf[4096], mpath[MAXPATHLEN];
341 	const char *dp;
342 	char *t1, *t2, *t3;
343 
344 	/*
345 	 * XXX
346 	 * MAXHOSTNAMELEN is in various places on various systems, including
347 	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
348 	 */
349 #ifndef MAXHOSTNAMELEN
350 #define	MAXHOSTNAMELEN	1024
351 #endif
352 	char host[MAXHOSTNAMELEN];
353 
354 	gp = sp->gp;
355 	if ((pw = getpwuid(uid = getuid())) == NULL) {
356 		msgq(sp, M_ERR,
357 		    "062|Information on user id %u not found", uid);
358 		return (1);
359 	}
360 
361 	if (opts_empty(sp, O_RECDIR, 0))
362 		return (1);
363 	dp = O_STR(sp, O_RECDIR);
364 	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
365 	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
366 		return (1);
367 
368 	/*
369 	 * XXX
370 	 * We keep an open lock on the file so that the recover option can
371 	 * distinguish between files that are live and those that need to
372 	 * be recovered.  There's an obvious window between the mkstemp call
373 	 * and the lock, but it's pretty small.
374 	 */
375 	ep = sp->ep;
376 	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
377 		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
378 	if (!issync) {
379 		/* Save the recover file descriptor, and mail path. */
380 		ep->rcv_fd = fd;
381 		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
382 			msgq(sp, M_SYSERR, NULL);
383 			goto err;
384 		}
385 		cp_path = ep->rcv_path;
386 	}
387 
388 	/*
389 	 * XXX
390 	 * We can't use stdio(3) here.  The problem is that we may be using
391 	 * fcntl(2), so if ANY file descriptor into the file is closed, the
392 	 * lock is lost.  So, we could never close the FILE *, even if we
393 	 * dup'd the fd first.
394 	 */
395 	t = sp->frp->name;
396 	if ((p = strrchr(t, '/')) == NULL)
397 		p = t;
398 	else
399 		++p;
400 	(void)time(&now);
401 	(void)gethostname(host, sizeof(host));
402 	len = snprintf(buf, sizeof(buf),
403 	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
404 	    VI_FHEADER, t,			/* Non-standard. */
405 	    VI_PHEADER, cp_path,		/* Non-standard. */
406 	    "Reply-To: root",
407 	    "From: root (Nvi recovery program)",
408 	    "To: ", pw->pw_name,
409 	    "Subject: Nvi saved the file ", p,
410 	    "Precedence: bulk");		/* For vacation(1). */
411 	if (len > sizeof(buf) - 1)
412 		goto lerr;
413 	if ((size_t)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 __P((SCR *));
488  */
489 int
490 rcv_list(SCR *sp)
491 {
492 	struct dirent *dp;
493 	struct stat sb;
494 	DIR *dirp;
495 	FILE *fp;
496 	int found;
497 	char *p, *t;
498 	const char *d;
499 	char file[MAXPATHLEN], path[MAXPATHLEN];
500 
501 	/* Open the recovery directory for reading. */
502 	if (opts_empty(sp, O_RECDIR, 0))
503 		return (1);
504 	d = O_STR(sp, O_RECDIR);
505 	if (chdir(d) || (dirp = opendir(".")) == NULL) {
506 		msgq_str(sp, M_SYSERR, d, "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("%s: No files to recover\n", sp->gp->progname);
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(SCR *sp, FREF *frp)
595 {
596 	struct dirent *dp;
597 	struct stat sb;
598 	DIR *dirp;
599 	EXF *ep;
600 	time_t rec_mtime;
601 	int fd, found, locked = 0, requested, sv_fd;
602 	char *name, *p, *t, *recp, *pathp;
603 	const char *rp;
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_ERR, 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 	free(pathp);
772 	return (0);
773 }
774 
775 /*
776  * rcv_copy --
777  *	Copy a recovery file.
778  */
779 static int
780 rcv_copy(SCR *sp, int wfd, char *fname)
781 {
782 	int nr, nw, off, rfd;
783 	char buf[8 * 1024];
784 
785 	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
786 		goto err;
787 	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
788 		for (off = 0; nr; nr -= nw, off += nw)
789 			if ((nw = write(wfd, buf + off, nr)) < 0)
790 				goto err;
791 	if (nr == 0)
792 		return (0);
793 
794 err:	msgq_str(sp, M_SYSERR, fname, "%s");
795 	return (1);
796 }
797 
798 /*
799  * rcv_gets --
800  *	Fgets(3) for a file descriptor.
801  */
802 static char *
803 rcv_gets(char *buf, size_t len, int fd)
804 {
805 	int nr;
806 	char *p;
807 
808 	if ((nr = read(fd, buf, len - 1)) == -1)
809 		return (NULL);
810 	if ((p = strchr(buf, '\n')) == NULL)
811 		return (NULL);
812 	(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
813 	return (buf);
814 }
815 
816 /*
817  * rcv_mktemp --
818  *	Paranoid make temporary file routine.
819  */
820 static int
821 rcv_mktemp(SCR *sp, char *path, const char *dname, int perms)
822 {
823 	int fd;
824 
825 	/*
826 	 * !!!
827 	 * We expect mkstemp(3) to set the permissions correctly.  On
828 	 * historic System V systems, mkstemp didn't.  Do it here, on
829 	 * GP's.
830 	 *
831 	 * XXX
832 	 * The variable perms should really be a mode_t, and it would
833 	 * be nice to use fchmod(2) instead of chmod(2), here.
834 	 */
835 	if ((fd = mkstemp(path)) == -1)
836 		msgq_str(sp, M_SYSERR, dname, "%s");
837 	else
838 		(void)chmod(path, perms);
839 	return (fd);
840 }
841 
842 /*
843  * rcv_email --
844  *	Send email.
845  */
846 static void
847 rcv_email(SCR *sp, char *fname)
848 {
849 	struct stat sb;
850 	char buf[MAXPATHLEN * 2 + 20];
851 
852 	if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
853 		msgq_str(sp, M_SYSERR,
854 		    _PATH_SENDMAIL, "071|not sending email: %s");
855 	else {
856 		/*
857 		 * !!!
858 		 * If you need to port this to a system that doesn't have
859 		 * sendmail, the -t flag causes sendmail to read the message
860 		 * for the recipients instead of specifying them some other
861 		 * way.
862 		 */
863 		(void)snprintf(buf, sizeof(buf),
864 		    "%s -t < %s", _PATH_SENDMAIL, fname);
865 		(void)system(buf);
866 	}
867 }
868