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
rcv_tmp(SCR * sp,EXF * ep,char * name)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
rcv_init(SCR * sp)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
rcv_sync(SCR * sp,u_int flags)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
rcv_mailfile(SCR * sp,int issync,char * cp_path)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
rcv_list(SCR * sp)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
rcv_read(SCR * sp,FREF * frp)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 #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC
705 timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) {
706 #elif defined HAVE_STRUCT_STAT_ST_MTIM
707 timespeccmp(&rec_mtim, &sb.st_mtim, <)) {
708 #else
709 rec_mtim.tv_sec < sb.st_mtime) {
710 #endif
711 p = recp;
712 t = pathp;
713 recp = recpath;
714 pathp = path;
715 if (p != NULL) {
716 free(p);
717 free(t);
718 }
719 #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC
720 rec_mtim = sb.st_mtimespec;
721 #elif defined HAVE_STRUCT_STAT_ST_MTIM
722 rec_mtim = sb.st_mtim;
723 #else
724 rec_mtim.tv_sec = sb.st_mtime;
725 #endif
726 if (sv_fd != -1)
727 (void)close(sv_fd);
728 sv_fd = dup(fileno(fp));
729 } else {
730 next: free(recpath);
731 free(path);
732 }
733 (void)fclose(fp);
734 free(file);
735 }
736 (void)closedir(dirp);
737
738 if (recp == NULL) {
739 msgq_str(sp, M_INFO, name,
740 "068|No files named %s, readable by you, to recover");
741 return (1);
742 }
743 if (found) {
744 if (requested > 1)
745 msgq(sp, M_INFO,
746 "069|There are older versions of this file for you to recover");
747 if (found > requested)
748 msgq(sp, M_INFO,
749 "070|There are other files for you to recover");
750 }
751
752 /*
753 * Create the FREF structure, start the btree file.
754 *
755 * XXX
756 * file_init() is going to set ep->rcv_path.
757 */
758 if (file_init(sp, frp, pathp, 0)) {
759 free(recp);
760 free(pathp);
761 (void)close(sv_fd);
762 return (1);
763 }
764 free(pathp);
765
766 /*
767 * We keep an open lock on the file so that the recover option can
768 * distinguish between files that are live and those that need to
769 * be recovered. The lock is already acquired, just copy it.
770 */
771 ep = sp->ep;
772 ep->rcv_mpath = recp;
773 ep->rcv_fd = sv_fd;
774 if (!locked)
775 F_SET(frp, FR_UNLOCKED);
776
777 /* We believe the file is recoverable. */
778 F_SET(ep, F_RCV_ON);
779 return (0);
780 }
781
782 /*
783 * rcv_copy --
784 * Copy a recovery file.
785 */
786 static int
787 rcv_copy(SCR *sp, int wfd, char *fname)
788 {
789 int nr, nw, off, rfd;
790 char buf[8 * 1024];
791
792 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
793 goto err;
794 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
795 for (off = 0; nr; nr -= nw, off += nw)
796 if ((nw = write(wfd, buf + off, nr)) < 0)
797 goto err;
798 if (nr == 0)
799 return (0);
800
801 err: msgq_str(sp, M_SYSERR, fname, "%s");
802 return (1);
803 }
804
805 /*
806 * rcv_mktemp --
807 * Paranoid make temporary file routine.
808 */
809 static int
810 rcv_mktemp(SCR *sp, char *path, char *dname)
811 {
812 int fd;
813
814 if ((fd = mkstemp(path)) == -1)
815 msgq_str(sp, M_SYSERR, dname, "%s");
816 return (fd);
817 }
818
819 /*
820 * rcv_email --
821 * Send email.
822 */
823 static void
824 rcv_email(SCR *sp, char *fname)
825 {
826 char *buf;
827
828 if (asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname) == -1) {
829 msgq_str(sp, M_ERR, strerror(errno),
830 "071|not sending email: %s");
831 return;
832 }
833 (void)system(buf);
834 free(buf);
835 }
836
837 /*
838 * rcv_dlnwrite --
839 * Encode a string into an X-vi-data line and write it.
840 */
841 static int
842 rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp)
843 {
844 char *bp = NULL, *p;
845 size_t blen = 0;
846 size_t dlen, len;
847 int plen, xlen;
848
849 len = strlen(src);
850 dlen = strlen(dtype);
851 GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2);
852 (void)memcpy(bp, dtype, dlen);
853 bp[dlen] = ';';
854 if ((xlen = b64_ntop((u_char *)src,
855 len, bp + dlen + 1, blen)) == -1)
856 goto err;
857 xlen += dlen + 1;
858
859 /* Output as an MIME folding header. */
860 if ((plen = fprintf(fp, VI_DHEADER " %.*s\n",
861 FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0)
862 goto err;
863 plen -= (int)sizeof(VI_DHEADER) + 1;
864 for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) {
865 p += plen;
866 if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0)
867 goto err;
868 plen -= 2;
869 }
870 FREE_SPACE(sp, bp, blen);
871 return (0);
872
873 err: FREE_SPACE(sp, bp, blen);
874 return (1);
875 alloc_err:
876 msgq(sp, M_SYSERR, NULL);
877 return (-1);
878 }
879
880 /*
881 * rcv_dlnread --
882 * Read an X-vi-data line and decode it.
883 */
884 static int
885 rcv_dlnread(SCR *sp, char **dtypep,
886 char **datap, /* free *datap if != NULL after use. */
887 FILE *fp)
888 {
889 int ch;
890 char buf[1024];
891 char *bp = NULL, *p, *src;
892 size_t blen = 0;
893 size_t len, off, dlen;
894 char *dtype, *data;
895 int xlen;
896
897 if (fgets(buf, sizeof(buf), fp) == NULL)
898 return (1);
899 if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) {
900 *dtypep = NULL;
901 *datap = NULL;
902 return (0);
903 }
904
905 /* Fetch an MIME folding header. */
906 len = strlen(buf) - sizeof(VI_DHEADER) + 1;
907 GET_SPACE_GOTOC(sp, bp, blen, len);
908 (void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len);
909 p = bp + len;
910 while ((ch = fgetc(fp)) == ' ') {
911 if (fgets(buf, sizeof(buf), fp) == NULL)
912 goto err;
913 off = strlen(buf);
914 len += off;
915 ADD_SPACE_GOTOC(sp, bp, blen, len);
916 p = bp + len - off;
917 (void)memcpy(p, buf, off);
918 }
919 bp[len] = '\0';
920 (void)ungetc(ch, fp);
921
922 for (p = bp; *p == ' ' || *p == '\n'; p++);
923 if ((src = strchr(p, ';')) == NULL)
924 goto err;
925 dlen = src - p;
926 src += 1;
927 len -= src - bp;
928
929 /* Memory looks like: "<data>\0<dtype>\0". */
930 MALLOC(sp, data, dlen + len / 4 * 3 + 2);
931 if (data == NULL)
932 goto err;
933 if ((xlen = (b64_pton(p + dlen + 1,
934 (u_char *)data, len / 4 * 3 + 1))) == -1) {
935 free(data);
936 goto err;
937 }
938 data[xlen] = '\0';
939 dtype = data + xlen + 1;
940 (void)memcpy(dtype, p, dlen);
941 dtype[dlen] = '\0';
942 FREE_SPACE(sp, bp, blen);
943 *dtypep = dtype;
944 *datap = data;
945 return (0);
946
947 err: FREE_SPACE(sp, bp, blen);
948 return (1);
949 alloc_err:
950 msgq(sp, M_SYSERR, NULL);
951 return (-1);
952 }
953