]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/nvi/common/recover.c
Update ELF Tool Chain to upstream r3769
[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 #ifndef lint
13 static 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
105 static int       rcv_copy(SCR *, int, char *);
106 static void      rcv_email(SCR *, char *);
107 static int       rcv_mailfile(SCR *, int, char *);
108 static int       rcv_mktemp(SCR *, char *, char *);
109 static int       rcv_dlnwrite(SCR *, const char *, const char *, FILE *);
110 static 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  */
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(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(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                 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) {
306 err:            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  */
320 static int
321 rcv_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. */
468 wout:           *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);
484 werr:           msgq(sp, M_SYSERR, "065|Recovery file");
485                 goto err;
486         }
487         free(buf);
488         return (0);
489
490 err:    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  */
507 int
508 rcv_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. */
596 next:           (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  */
614 int
615 rcv_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 {
734 next:                   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  */
792 static int
793 rcv_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
810 err:    msgq_str(sp, M_SYSERR, fname, "%s");
811         return (1);
812 }
813
814 /*
815  * rcv_mktemp --
816  *      Paranoid make temporary file routine.
817  */
818 static int
819 rcv_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  */
835 static void
836 rcv_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  */
856 static int
857 rcv_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
892 err:    FREE_SPACE(sp, bp, blen);
893         return (1);
894 alloc_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  */
903 static int
904 rcv_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
968 err:    FREE_SPACE(sp, bp, blen);
969         return (1);
970 alloc_err:
971         msgq(sp, M_SYSERR, NULL);
972         return (-1);
973 }