nvi: Upgrade from version 1.79 to 2.1.1 (multibyte support)
[dragonfly.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 #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 }