xref: /freebsd/contrib/nvi/common/recover.c (revision 3b8f08459569bf0faa21473e5cec2491e95c9349)
1 /*-
2  * Copyright (c) 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1993, 1994, 1995, 1996
5  *	Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9 
10 #include "config.h"
11 
12 #ifndef lint
13 static const char sccsid[] = "$Id: recover.c,v 11.2 2012/10/09 08:06:58 zy Exp $";
14 #endif /* not lint */
15 
16 #include <sys/types.h>
17 #include <sys/queue.h>
18 #include <sys/stat.h>
19 
20 /*
21  * We include <sys/file.h>, because the open #defines were found there
22  * on historical systems.  We also include <fcntl.h> because the open(2)
23  * #defines are found there on newer systems.
24  */
25 #include <sys/file.h>
26 
27 #include <bitstring.h>
28 #include <dirent.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <limits.h>
32 #include <pwd.h>
33 #include <netinet/in.h>		/* Required by resolv.h. */
34 #include <resolv.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <time.h>
39 #include <unistd.h>
40 
41 #include "../ex/version.h"
42 #include "common.h"
43 #include "pathnames.h"
44 
45 /*
46  * Recovery code.
47  *
48  * The basic scheme is as follows.  In the EXF structure, we maintain full
49  * paths of a b+tree file and a mail recovery file.  The former is the file
50  * used as backing store by the DB package.  The latter is the file that
51  * contains an email message to be sent to the user if we crash.  The two
52  * simple states of recovery are:
53  *
54  *	+ first starting the edit session:
55  *		the b+tree file exists and is mode 700, the mail recovery
56  *		file doesn't exist.
57  *	+ after the file has been modified:
58  *		the b+tree file exists and is mode 600, the mail recovery
59  *		file exists, and is exclusively locked.
60  *
61  * In the EXF structure we maintain a file descriptor that is the locked
62  * file descriptor for the mail recovery file.
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 additional
94  *
95  *	X-vi-data: <file|path>;<base64 encoded path>
96  *
97  * MIME headers; the folding character is limited to ' '.
98  *
99  * Btree files are named "vi.XXXXXX" and recovery files are named
100  * "recover.XXXXXX".
101  */
102 
103 #define	VI_DHEADER	"X-vi-data:"
104 
105 static int	 rcv_copy __P((SCR *, int, char *));
106 static void	 rcv_email __P((SCR *, char *));
107 static int	 rcv_mailfile __P((SCR *, int, char *));
108 static int	 rcv_mktemp __P((SCR *, char *, char *));
109 static int	 rcv_dlnwrite __P((SCR *, const char *, const char *, FILE *));
110 static int	 rcv_dlnread __P((SCR *, char **, char **, FILE *));
111 
112 /*
113  * rcv_tmp --
114  *	Build a file name that will be used as the recovery file.
115  *
116  * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
117  */
118 int
119 rcv_tmp(
120 	SCR *sp,
121 	EXF *ep,
122 	char *name)
123 {
124 	struct stat sb;
125 	int fd;
126 	char *dp, *path;
127 
128 	/*
129 	 * !!!
130 	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
131 	 *
132 	 *
133 	 * If the recovery directory doesn't exist, try and create it.  As
134 	 * the recovery files are themselves protected from reading/writing
135 	 * by other than the owner, the worst that can happen is that a user
136 	 * would have permission to remove other user's recovery files.  If
137 	 * the sticky bit has the BSD semantics, that too will be impossible.
138 	 */
139 	if (opts_empty(sp, O_RECDIR, 0))
140 		goto err;
141 	dp = O_STR(sp, O_RECDIR);
142 	if (stat(dp, &sb)) {
143 		if (errno != ENOENT || mkdir(dp, 0)) {
144 			msgq(sp, M_SYSERR, "%s", dp);
145 			goto err;
146 		}
147 		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
148 	}
149 
150 	if ((path = join(dp, "vi.XXXXXX")) == NULL)
151 		goto err;
152 	if ((fd = rcv_mktemp(sp, path, dp)) == -1) {
153 		free(path);
154 		goto err;
155 	}
156 	(void)fchmod(fd, S_IRWXU);
157 	(void)close(fd);
158 
159 	ep->rcv_path = path;
160 	if (0) {
161 err:		msgq(sp, M_ERR,
162 		    "056|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 __P((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 		    "057|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 			    "058|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 	    "059|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 __P((SCR *, u_int));
238  */
239 int
240 rcv_sync(
241 	SCR *sp,
242 	u_int flags)
243 {
244 	EXF *ep;
245 	int fd, rval;
246 	char *dp, *buf;
247 
248 	/* Make sure that there's something to recover/sync. */
249 	ep = sp->ep;
250 	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
251 		return (0);
252 
253 	/* Sync the file if it's been modified. */
254 	if (F_ISSET(ep, F_MODIFIED)) {
255 		SIGBLOCK;
256 		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
257 			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
258 			msgq_str(sp, M_SYSERR,
259 			    ep->rcv_path, "060|File backup failed: %s");
260 			SIGUNBLOCK;
261 			return (1);
262 		}
263 		SIGUNBLOCK;
264 
265 		/* REQUEST: don't remove backing file on exit. */
266 		if (LF_ISSET(RCV_PRESERVE))
267 			F_SET(ep, F_RCV_NORM);
268 
269 		/* REQUEST: send email. */
270 		if (LF_ISSET(RCV_EMAIL))
271 			rcv_email(sp, ep->rcv_mpath);
272 	}
273 
274 	/*
275 	 * !!!
276 	 * Each time the user exec's :preserve, we have to snapshot all of
277 	 * the recovery information, i.e. it's like the user re-edited the
278 	 * file.  We copy the DB(3) backing file, and then create a new mail
279 	 * recovery file, it's simpler than exiting and reopening all of the
280 	 * underlying files.
281 	 *
282 	 * REQUEST: snapshot the file.
283 	 */
284 	rval = 0;
285 	if (LF_ISSET(RCV_SNAPSHOT)) {
286 		if (opts_empty(sp, O_RECDIR, 0))
287 			goto err;
288 		dp = O_STR(sp, O_RECDIR);
289 		if ((buf = join(dp, "vi.XXXXXX")) == NULL) {
290 			msgq(sp, M_SYSERR, NULL);
291 			goto err;
292 		}
293 		if ((fd = rcv_mktemp(sp, buf, dp)) == -1) {
294 			free(buf);
295 			goto err;
296 		}
297 		sp->gp->scr_busy(sp,
298 		    "061|Copying file for recovery...", BUSY_ON);
299 		if (rcv_copy(sp, fd, ep->rcv_path) ||
300 		    close(fd) || rcv_mailfile(sp, 1, buf)) {
301 			(void)unlink(buf);
302 			(void)close(fd);
303 			rval = 1;
304 		}
305 		free(buf);
306 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
307 	}
308 	if (0) {
309 err:		rval = 1;
310 	}
311 
312 	/* REQUEST: end the file session. */
313 	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
314 		rval = 1;
315 
316 	return (rval);
317 }
318 
319 /*
320  * rcv_mailfile --
321  *	Build the file to mail to the user.
322  */
323 static int
324 rcv_mailfile(
325 	SCR *sp,
326 	int issync,
327 	char *cp_path)
328 {
329 	EXF *ep;
330 	GS *gp;
331 	struct passwd *pw;
332 	int len;
333 	time_t now;
334 	uid_t uid;
335 	int fd;
336 	FILE *fp;
337 	char *dp, *p, *t, *qt, *buf, *mpath;
338 	char *t1, *t2, *t3;
339 	int st;
340 
341 	/*
342 	 * XXX
343 	 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3)
344 	 * first, then fallback to _POSIX_HOST_NAME_MAX.
345 	 */
346 	char *host;
347 	long hostmax = sysconf(_SC_HOST_NAME_MAX);
348 	if (hostmax < 0)
349 		hostmax = _POSIX_HOST_NAME_MAX;
350 
351 	gp = sp->gp;
352 	if ((pw = getpwuid(uid = getuid())) == NULL) {
353 		msgq(sp, M_ERR,
354 		    "062|Information on user id %u not found", uid);
355 		return (1);
356 	}
357 
358 	if (opts_empty(sp, O_RECDIR, 0))
359 		return (1);
360 	dp = O_STR(sp, O_RECDIR);
361 	if ((mpath = join(dp, "recover.XXXXXX")) == NULL) {
362 		msgq(sp, M_SYSERR, NULL);
363 		return (1);
364 	}
365 	if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) {
366 		free(mpath);
367 		return (1);
368 	}
369 	if ((fp = fdopen(fd, "w")) == NULL) {
370 		free(mpath);
371 		close(fd);
372 		return (1);
373 	}
374 
375 	/*
376 	 * XXX
377 	 * We keep an open lock on the file so that the recover option can
378 	 * distinguish between files that are live and those that need to
379 	 * be recovered.  There's an obvious window between the mkstemp call
380 	 * and the lock, but it's pretty small.
381 	 */
382 	ep = sp->ep;
383 	if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS)
384 		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
385 	if (!issync) {
386 		/* Save the recover file descriptor, and mail path. */
387 		ep->rcv_fd = dup(fd);
388 		ep->rcv_mpath = mpath;
389 		cp_path = ep->rcv_path;
390 	}
391 
392 	t = sp->frp->name;
393 	if ((p = strrchr(t, '/')) == NULL)
394 		p = t;
395 	else
396 		++p;
397 	(void)time(&now);
398 
399 	if ((st = rcv_dlnwrite(sp, "file", t, fp))) {
400 		if (st == 1)
401 			goto werr;
402 		goto err;
403 	}
404 	if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) {
405 		if (st == 1)
406 			goto werr;
407 		goto err;
408 	}
409 
410 	MALLOC(sp, host, char *, hostmax + 1);
411 	if (host == NULL)
412 		goto err;
413 	(void)gethostname(host, hostmax + 1);
414 
415 	len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n",
416 	    "From: root@", host, " (Nvi recovery program)",
417 	    "To: ", pw->pw_name, "@", host,
418 	    "Subject: Nvi saved the file ", p,
419 	    "Precedence: bulk");		/* For vacation(1). */
420 	if (len < 0) {
421 		free(host);
422 		goto werr;
423 	}
424 
425 	if ((qt = quote(t)) == NULL) {
426 		free(host);
427 		msgq(sp, M_SYSERR, NULL);
428 		goto err;
429 	}
430 	len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
431 	    "On ", ctime(&now), ", the user ", pw->pw_name,
432 	    " was editing a file named ", t, " on the machine ",
433 	    host, ", when it was saved for recovery. ",
434 	    "You can recover most, if not all, of the changes ",
435 	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
436 	    gp->progname, " -r ", qt);
437 	free(qt);
438 	free(host);
439 	if (buf == NULL) {
440 		msgq(sp, M_SYSERR, NULL);
441 		goto err;
442 	}
443 
444 	/*
445 	 * Format the message.  (Yes, I know it's silly.)
446 	 * Requires that the message end in a <newline>.
447 	 */
448 #define	FMTCOLS	60
449 	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
450 		/* Check for a short length. */
451 		if (len <= FMTCOLS) {
452 			t2 = t1 + (len - 1);
453 			goto wout;
454 		}
455 
456 		/* Check for a required <newline>. */
457 		t2 = strchr(t1, '\n');
458 		if (t2 - t1 <= FMTCOLS)
459 			goto wout;
460 
461 		/* Find the closest space, if any. */
462 		for (t3 = t2; t2 > t1; --t2)
463 			if (*t2 == ' ') {
464 				if (t2 - t1 <= FMTCOLS)
465 					goto wout;
466 				t3 = t2;
467 			}
468 		t2 = t3;
469 
470 		/* t2 points to the last character to display. */
471 wout:		*t2++ = '\n';
472 
473 		/* t2 points one after the last character to display. */
474 		if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) {
475 			free(buf);
476 			goto werr;
477 		}
478 	}
479 
480 	if (issync) {
481 		fflush(fp);
482 		rcv_email(sp, mpath);
483 		free(mpath);
484 	}
485 	if (fclose(fp)) {
486 		free(buf);
487 werr:		msgq(sp, M_SYSERR, "065|Recovery file");
488 		goto err;
489 	}
490 	free(buf);
491 	return (0);
492 
493 err:	if (!issync)
494 		ep->rcv_fd = -1;
495 	if (fp != NULL)
496 		(void)fclose(fp);
497 	return (1);
498 }
499 
500 /*
501  *	people making love
502  *	never exactly the same
503  *	just like a snowflake
504  *
505  * rcv_list --
506  *	List the files that can be recovered by this user.
507  *
508  * PUBLIC: int rcv_list __P((SCR *));
509  */
510 int
511 rcv_list(SCR *sp)
512 {
513 	struct dirent *dp;
514 	struct stat sb;
515 	DIR *dirp;
516 	FILE *fp;
517 	int found;
518 	char *p, *file, *path;
519 	char *dtype, *data;
520 	int st;
521 
522 	/* Open the recovery directory for reading. */
523 	if (opts_empty(sp, O_RECDIR, 0))
524 		return (1);
525 	p = O_STR(sp, O_RECDIR);
526 	if (chdir(p) || (dirp = opendir(".")) == NULL) {
527 		msgq_str(sp, M_SYSERR, p, "recdir: %s");
528 		return (1);
529 	}
530 
531 	/* Read the directory. */
532 	for (found = 0; (dp = readdir(dirp)) != NULL;) {
533 		if (strncmp(dp->d_name, "recover.", 8))
534 			continue;
535 
536 		/* If it's readable, it's recoverable. */
537 		if ((fp = fopen(dp->d_name, "r")) == NULL)
538 			continue;
539 
540 		switch (file_lock(sp, NULL, fileno(fp), 1)) {
541 		case LOCK_FAILED:
542 			/*
543 			 * XXX
544 			 * Assume that a lock can't be acquired, but that we
545 			 * should permit recovery anyway.  If this is wrong,
546 			 * and someone else is using the file, we're going to
547 			 * die horribly.
548 			 */
549 			break;
550 		case LOCK_SUCCESS:
551 			break;
552 		case LOCK_UNAVAIL:
553 			/* If it's locked, it's live. */
554 			(void)fclose(fp);
555 			continue;
556 		}
557 
558 		/* Check the headers. */
559 		for (file = NULL, path = NULL;
560 		    file == NULL || path == NULL;) {
561 			if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
562 				if (st == 1)
563 					msgq_str(sp, M_ERR, dp->d_name,
564 					    "066|%s: malformed recovery file");
565 				goto next;
566 			}
567 			if (dtype == NULL)
568 				continue;
569 			if (!strcmp(dtype, "file"))
570 				file = data;
571 			else if (!strcmp(dtype, "path"))
572 				path = data;
573 			else
574 				free(data);
575 		}
576 
577 		/*
578 		 * If the file doesn't exist, it's an orphaned recovery file,
579 		 * toss it.
580 		 *
581 		 * XXX
582 		 * This can occur if the backup file was deleted and we crashed
583 		 * before deleting the email file.
584 		 */
585 		errno = 0;
586 		if (stat(path, &sb) &&
587 		    errno == ENOENT) {
588 			(void)unlink(dp->d_name);
589 			goto next;
590 		}
591 
592 		/* Get the last modification time and display. */
593 		(void)fstat(fileno(fp), &sb);
594 		(void)printf("%.24s: %s\n",
595 		    ctime(&sb.st_mtime), file);
596 		found = 1;
597 
598 		/* Close, discarding lock. */
599 next:		(void)fclose(fp);
600 		if (file != NULL)
601 			free(file);
602 		if (path != NULL)
603 			free(path);
604 	}
605 	if (found == 0)
606 		(void)printf("%s: No files to recover\n", sp->gp->progname);
607 	(void)closedir(dirp);
608 	return (0);
609 }
610 
611 /*
612  * rcv_read --
613  *	Start a recovered file as the file to edit.
614  *
615  * PUBLIC: int rcv_read __P((SCR *, FREF *));
616  */
617 int
618 rcv_read(
619 	SCR *sp,
620 	FREF *frp)
621 {
622 	struct dirent *dp;
623 	struct stat sb;
624 	DIR *dirp;
625 	FILE *fp;
626 	EXF *ep;
627 	struct timespec rec_mtim = { 0, 0 };
628 	int found, locked = 0, requested, sv_fd;
629 	char *name, *p, *t, *rp, *recp, *pathp;
630 	char *file, *path, *recpath;
631 	char *dtype, *data;
632 	int st;
633 
634 	if (opts_empty(sp, O_RECDIR, 0))
635 		return (1);
636 	rp = O_STR(sp, O_RECDIR);
637 	if ((dirp = opendir(rp)) == NULL) {
638 		msgq_str(sp, M_ERR, rp, "%s");
639 		return (1);
640 	}
641 
642 	name = frp->name;
643 	sv_fd = -1;
644 	recp = pathp = NULL;
645 	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
646 		if (strncmp(dp->d_name, "recover.", 8))
647 			continue;
648 		if ((recpath = join(rp, dp->d_name)) == NULL) {
649 			msgq(sp, M_SYSERR, NULL);
650 			continue;
651 		}
652 
653 		/* If it's readable, it's recoverable. */
654 		if ((fp = fopen(recpath, "r")) == NULL) {
655 			free(recpath);
656 			continue;
657 		}
658 
659 		switch (file_lock(sp, NULL, fileno(fp), 1)) {
660 		case LOCK_FAILED:
661 			/*
662 			 * XXX
663 			 * Assume that a lock can't be acquired, but that we
664 			 * should permit recovery anyway.  If this is wrong,
665 			 * and someone else is using the file, we're going to
666 			 * die horribly.
667 			 */
668 			locked = 0;
669 			break;
670 		case LOCK_SUCCESS:
671 			locked = 1;
672 			break;
673 		case LOCK_UNAVAIL:
674 			/* If it's locked, it's live. */
675 			(void)fclose(fp);
676 			continue;
677 		}
678 
679 		/* Check the headers. */
680 		for (file = NULL, path = NULL;
681 		    file == NULL || path == NULL;) {
682 			if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
683 				if (st == 1)
684 					msgq_str(sp, M_ERR, dp->d_name,
685 					    "067|%s: malformed recovery file");
686 				goto next;
687 			}
688 			if (dtype == NULL)
689 				continue;
690 			if (!strcmp(dtype, "file"))
691 				file = data;
692 			else if (!strcmp(dtype, "path"))
693 				path = data;
694 			else
695 				free(data);
696 		}
697 		++found;
698 
699 		/*
700 		 * If the file doesn't exist, it's an orphaned recovery file,
701 		 * toss it.
702 		 *
703 		 * XXX
704 		 * This can occur if the backup file was deleted and we crashed
705 		 * before deleting the email file.
706 		 */
707 		errno = 0;
708 		if (stat(path, &sb) &&
709 		    errno == ENOENT) {
710 			(void)unlink(dp->d_name);
711 			goto next;
712 		}
713 
714 		/* Check the file name. */
715 		if (strcmp(file, name))
716 			goto next;
717 
718 		++requested;
719 
720 		/* If we've found more than one, take the most recent. */
721 		(void)fstat(fileno(fp), &sb);
722 		if (recp == NULL ||
723 		    timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) {
724 			p = recp;
725 			t = pathp;
726 			recp = recpath;
727 			pathp = path;
728 			if (p != NULL) {
729 				free(p);
730 				free(t);
731 			}
732 			rec_mtim = sb.st_mtimespec;
733 			if (sv_fd != -1)
734 				(void)close(sv_fd);
735 			sv_fd = dup(fileno(fp));
736 		} else {
737 next:			free(recpath);
738 			if (path != NULL)
739 				free(path);
740 		}
741 		(void)fclose(fp);
742 		if (file != NULL)
743 			free(file);
744 	}
745 	(void)closedir(dirp);
746 
747 	if (recp == NULL) {
748 		msgq_str(sp, M_INFO, name,
749 		    "068|No files named %s, readable by you, to recover");
750 		return (1);
751 	}
752 	if (found) {
753 		if (requested > 1)
754 			msgq(sp, M_INFO,
755 	    "069|There are older versions of this file for you to recover");
756 		if (found > requested)
757 			msgq(sp, M_INFO,
758 			    "070|There are other files for you to recover");
759 	}
760 
761 	/*
762 	 * Create the FREF structure, start the btree file.
763 	 *
764 	 * XXX
765 	 * file_init() is going to set ep->rcv_path.
766 	 */
767 	if (file_init(sp, frp, pathp, 0)) {
768 		free(recp);
769 		free(pathp);
770 		(void)close(sv_fd);
771 		return (1);
772 	}
773 	free(pathp);
774 
775 	/*
776 	 * We keep an open lock on the file so that the recover option can
777 	 * distinguish between files that are live and those that need to
778 	 * be recovered.  The lock is already acquired, just copy it.
779 	 */
780 	ep = sp->ep;
781 	ep->rcv_mpath = recp;
782 	ep->rcv_fd = sv_fd;
783 	if (!locked)
784 		F_SET(frp, FR_UNLOCKED);
785 
786 	/* We believe the file is recoverable. */
787 	F_SET(ep, F_RCV_ON);
788 	return (0);
789 }
790 
791 /*
792  * rcv_copy --
793  *	Copy a recovery file.
794  */
795 static int
796 rcv_copy(
797 	SCR *sp,
798 	int wfd,
799 	char *fname)
800 {
801 	int nr, nw, off, rfd;
802 	char buf[8 * 1024];
803 
804 	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
805 		goto err;
806 	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
807 		for (off = 0; nr; nr -= nw, off += nw)
808 			if ((nw = write(wfd, buf + off, nr)) < 0)
809 				goto err;
810 	if (nr == 0)
811 		return (0);
812 
813 err:	msgq_str(sp, M_SYSERR, fname, "%s");
814 	return (1);
815 }
816 
817 /*
818  * rcv_mktemp --
819  *	Paranoid make temporary file routine.
820  */
821 static int
822 rcv_mktemp(
823 	SCR *sp,
824 	char *path,
825 	char *dname)
826 {
827 	int fd;
828 
829 	if ((fd = mkstemp(path)) == -1)
830 		msgq_str(sp, M_SYSERR, dname, "%s");
831 	return (fd);
832 }
833 
834 /*
835  * rcv_email --
836  *	Send email.
837  */
838 static void
839 rcv_email(
840 	SCR *sp,
841 	char *fname)
842 {
843 	char *buf;
844 
845 	(void)asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname);
846 	if (buf == NULL) {
847 		msgq_str(sp, M_ERR, strerror(errno),
848 		    "071|not sending email: %s");
849 		return;
850 	}
851 	(void)system(buf);
852 	free(buf);
853 }
854 
855 /*
856  * rcv_dlnwrite --
857  *	Encode a string into an X-vi-data line and write it.
858  */
859 static int
860 rcv_dlnwrite(
861 	SCR *sp,
862 	const char *dtype,
863 	const char *src,
864 	FILE *fp)
865 {
866 	char *bp = NULL, *p;
867 	size_t blen = 0;
868 	size_t dlen, len;
869 	int plen, xlen;
870 
871 	len = strlen(src);
872 	dlen = strlen(dtype);
873 	GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2);
874 	(void)memcpy(bp, dtype, dlen);
875 	bp[dlen] = ';';
876 	if ((xlen = b64_ntop((u_char *)src,
877 	    len, bp + dlen + 1, blen)) == -1)
878 		goto err;
879 	xlen += dlen + 1;
880 
881 	/* Output as an MIME folding header. */
882 	if ((plen = fprintf(fp, VI_DHEADER " %.*s\n",
883 	    FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0)
884 		goto err;
885 	plen -= (int)sizeof(VI_DHEADER) + 1;
886 	for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) {
887 		p += plen;
888 		if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0)
889 			goto err;
890 		plen -= 2;
891 	}
892 	FREE_SPACE(sp, bp, blen);
893 	return (0);
894 
895 err:	FREE_SPACE(sp, bp, blen);
896 	return (1);
897 alloc_err:
898 	msgq(sp, M_SYSERR, NULL);
899 	return (-1);
900 }
901 
902 /*
903  * rcv_dlnread --
904  *	Read an X-vi-data line and decode it.
905  */
906 static int
907 rcv_dlnread(
908 	SCR *sp,
909 	char **dtypep,
910 	char **datap,		/* free *datap if != NULL after use. */
911 	FILE *fp)
912 {
913 	int ch;
914 	char buf[1024];
915 	char *bp = NULL, *p, *src;
916 	size_t blen = 0;
917 	size_t len, off, dlen;
918 	char *dtype, *data;
919 	int xlen;
920 
921 	if (fgets(buf, sizeof(buf), fp) == NULL)
922 		return (1);
923 	if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) {
924 		*dtypep = NULL;
925 		*datap = NULL;
926 		return (0);
927 	}
928 
929 	/* Fetch an MIME folding header. */
930 	len = strlen(buf) - sizeof(VI_DHEADER) + 1;
931 	GET_SPACE_GOTOC(sp, bp, blen, len);
932 	(void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len);
933 	p = bp + len;
934 	while ((ch = fgetc(fp)) == ' ') {
935 		if (fgets(buf, sizeof(buf), fp) == NULL)
936 			goto err;
937 		off = strlen(buf);
938 		len += off;
939 		ADD_SPACE_GOTOC(sp, bp, blen, len);
940 		p = bp + len - off;
941 		(void)memcpy(p, buf, off);
942 	}
943 	bp[len] = '\0';
944 	(void)ungetc(ch, fp);
945 
946 	for (p = bp; *p == ' ' || *p == '\n'; p++);
947 	if ((src = strchr(p, ';')) == NULL)
948 		goto err;
949 	dlen = src - p;
950 	src += 1;
951 	len -= src - bp;
952 
953 	/* Memory looks like: "<data>\0<dtype>\0". */
954 	MALLOC(sp, data, char *, dlen + len / 4 * 3 + 2);
955 	if (data == NULL)
956 		goto err;
957 	if ((xlen = (b64_pton(p + dlen + 1,
958 	    (u_char *)data, len / 4 * 3 + 1))) == -1) {
959 		free(data);
960 		goto err;
961 	}
962 	data[xlen] = '\0';
963 	dtype = data + xlen + 1;
964 	(void)memcpy(dtype, p, dlen);
965 	dtype[dlen] = '\0';
966 	FREE_SPACE(sp, bp, blen);
967 	*dtypep = dtype;
968 	*datap = data;
969 	return (0);
970 
971 err: 	FREE_SPACE(sp, bp, blen);
972 	return (1);
973 alloc_err:
974 	msgq(sp, M_SYSERR, NULL);
975 	return (-1);
976 }
977