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