10SN/A/*-
2553SN/A * Copyright (c) 1993, 1994
30SN/A *	The Regents of the University of California.  All rights reserved.
40SN/A * Copyright (c) 1993, 1994, 1995, 1996
50SN/A *	Keith Bostic.  All rights reserved.
60SN/A *
7553SN/A * See the LICENSE file for redistribution information.
80SN/A */
9553SN/A
100SN/A#include "config.h"
110SN/A
120SN/A#ifndef lint
130SN/Astatic const char sccsid[] = "$Id: recover.c,v 11.2 2012/10/09 08:06:58 zy Exp $";
140SN/A#endif /* not lint */
150SN/A
160SN/A#include <sys/types.h>
170SN/A#include <sys/queue.h>
180SN/A#include <sys/stat.h>
190SN/A
200SN/A/*
21553SN/A * We include <sys/file.h>, because the open #defines were found there
22553SN/A * on historical systems.  We also include <fcntl.h> because the open(2)
23553SN/A * #defines are found there on newer systems.
240SN/A */
250SN/A#include <sys/file.h>
260SN/A
270SN/A#include <bitstring.h>
28110SN/A#include <dirent.h>
29110SN/A#include <errno.h>
30110SN/A#include <fcntl.h>
310SN/A#include <limits.h>
32110SN/A#include <pwd.h>
33110SN/A#include <netinet/in.h>		/* Required by resolv.h. */
34110SN/A#include <resolv.h>
35110SN/A#include <stdio.h>
36580SN/A#include <stdlib.h>
37110SN/A#include <string.h>
38110SN/A#include <time.h>
39110SN/A#include <unistd.h>
40110SN/A
41110SN/A#include "../ex/version.h"
420SN/A#include "common.h"
43110SN/A#include "pathnames.h"
44110SN/A
450SN/A/*
46110SN/A * Recovery code.
470SN/A *
480SN/A * The basic scheme is as follows.  In the EXF structure, we maintain full
49110SN/A * paths of a b+tree file and a mail recovery file.  The former is the file
50110SN/A * used as backing store by the DB package.  The latter is the file that
510SN/A * contains an email message to be sent to the user if we crash.  The two
52110SN/A * simple states of recovery are:
530SN/A *
54110SN/A *	+ first starting the edit session:
55110SN/A *		the b+tree file exists and is mode 700, the mail recovery
56110SN/A *		file doesn't exist.
570SN/A *	+ after the file has been modified:
58110SN/A *		the b+tree file exists and is mode 600, the mail recovery
590SN/A *		file exists, and is exclusively locked.
60110SN/A *
61110SN/A * In the EXF structure we maintain a file descriptor that is the locked
62110SN/A * file descriptor for the mail recovery file.
630SN/A *
64110SN/A * To find out if a recovery file/backing file pair are in use, try to get
650SN/A * 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
105static int	 rcv_copy __P((SCR *, int, char *));
106static void	 rcv_email __P((SCR *, char *));
107static int	 rcv_mailfile __P((SCR *, int, char *));
108static int	 rcv_mktemp __P((SCR *, char *, char *));
109static int	 rcv_dlnwrite __P((SCR *, const char *, const char *, FILE *));
110static 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 */
118int
119rcv_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) {
161err:		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 */
177int
178rcv_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
224err:	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 */
239int
240rcv_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) {
309err:		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 */
323static int
324rcv_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. */
471wout:		*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);
487werr:		msgq(sp, M_SYSERR, "065|Recovery file");
488		goto err;
489	}
490	free(buf);
491	return (0);
492
493err:	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 */
510int
511rcv_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. */
599next:		(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 */
617int
618rcv_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 {
737next:			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 */
795static int
796rcv_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
813err:	msgq_str(sp, M_SYSERR, fname, "%s");
814	return (1);
815}
816
817/*
818 * rcv_mktemp --
819 *	Paranoid make temporary file routine.
820 */
821static int
822rcv_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 */
838static void
839rcv_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 */
859static int
860rcv_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
895err:	FREE_SPACE(sp, bp, blen);
896	return (1);
897alloc_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 */
906static int
907rcv_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
971err: 	FREE_SPACE(sp, bp, blen);
972	return (1);
973alloc_err:
974	msgq(sp, M_SYSERR, NULL);
975	return (-1);
976}
977