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