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
13static 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
112static int	 rcv_copy __P((SCR *, int, char *));
113static void	 rcv_email __P((SCR *, char *));
114static char	*rcv_gets __P((char *, size_t, int));
115static int	 rcv_mailfile __P((SCR *, int, char *));
116static 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 */
124int
125rcv_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);
172err:		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 */
188int
189rcv_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
236err:	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 */
251int
252rcv_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) {
315err:		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 */
329static int
330rcv_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) {
426lerr:		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. */
457wout:		*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)) {
467werr:			msgq(sp, M_SYSERR, "065|Recovery file");
468			goto err;
469		}
470	}
471	return (0);
472
473err:	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 */
490int
491rcv_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. */
579next:		(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 */
593int
594rcv_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
728next:			(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 */
779static int
780rcv_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
797err:	msgq_str(sp, M_SYSERR, fname, "%s");
798	return (1);
799}
800
801/*
802 * rcv_gets --
803 *	Fgets(3) for a file descriptor.
804 */
805static char *
806rcv_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 */
826static int
827rcv_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 */
855static void
856rcv_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