add a .gitignore file
[nvi.git] / common / recover.c
blobb6746956eb14c859c46229041177a34f5cf2b864
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.
7 * See the LICENSE file for redistribution information.
8 */
10 #include "config.h"
12 #ifndef lint
13 static const char sccsid[] = "$Id: recover.c,v 10.31 2001/11/01 15:24:44 skimo Exp $ (Berkeley) $Date: 2001/11/01 15:24:44 $";
14 #endif /* not lint */
16 #include <sys/param.h>
17 #include <sys/types.h> /* XXX: param.h may not have included types.h */
18 #include <sys/queue.h>
19 #include <sys/stat.h>
22 * We include <sys/file.h>, because the open #defines were found there
23 * on historical systems. We also include <fcntl.h> because the open(2)
24 * #defines are found there on newer systems.
26 #include <sys/file.h>
28 #include <bitstring.h>
29 #include <dirent.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <limits.h>
33 #include <pwd.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38 #include <unistd.h>
40 #include "common.h"
41 #include "pathnames.h"
44 * Recovery code.
46 * The basic scheme is as follows. In the EXF structure, we maintain full
47 * paths of a b+tree file and a mail recovery file. The former is the file
48 * used as backing store by the DB package. The latter is the file that
49 * contains an email message to be sent to the user if we crash. The two
50 * simple states of recovery are:
52 * + first starting the edit session:
53 * the b+tree file exists and is mode 700, the mail recovery
54 * file doesn't exist.
55 * + after the file has been modified:
56 * the b+tree file exists and is mode 600, the mail recovery
57 * file exists, and is exclusively locked.
59 * In the EXF structure we maintain a file descriptor that is the locked
60 * file descriptor for the mail recovery file. NOTE: we sometimes have to
61 * do locking with fcntl(2). This is a problem because if you close(2) any
62 * file descriptor associated with the file, ALL of the locks go away. Be
63 * sure to remember that if you have to modify the recovery code. (It has
64 * been rhetorically asked of what the designers could have been thinking
65 * when they did that interface. The answer is simple: they weren't.)
67 * To find out if a recovery file/backing file pair are in use, try to get
68 * a lock on the recovery file.
70 * To find out if a backing file can be deleted at boot time, check for an
71 * owner execute bit. (Yes, I know it's ugly, but it's either that or put
72 * special stuff into the backing file itself, or correlate the files at
73 * boot time, neither of which looks like fun.) Note also that there's a
74 * window between when the file is created and the X bit is set. It's small,
75 * but it's there. To fix the window, check for 0 length files as well.
77 * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
78 * this DOES NOT mean that any initialization has been done, only that we
79 * haven't yet failed at setting up or doing recovery.
81 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
82 * If that bit is not set when ending a file session:
83 * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
84 * they are unlink(2)'d, and free(3)'d.
85 * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
87 * The backing b+tree file is set up when a file is first edited, so that
88 * the DB package can use it for on-disk caching and/or to snapshot the
89 * file. When the file is first modified, the mail recovery file is created,
90 * the backing file permissions are updated, the file is sync(2)'d to disk,
91 * and the timer is started. Then, at RCV_PERIOD second intervals, the
92 * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
93 * means that the data structures (SCR, EXF, the underlying tree structures)
94 * must be consistent when the signal arrives.
96 * The recovery mail file contains normal mail headers, with two additions,
97 * which occur in THIS order, as the FIRST TWO headers:
99 * X-vi-recover-file: file_name
100 * X-vi-recover-path: recover_path
102 * Since newlines delimit the headers, this means that file names cannot have
103 * newlines in them, but that's probably okay. As these files aren't intended
104 * to be long-lived, changing their format won't be too painful.
106 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
109 #define VI_FHEADER "X-vi-recover-file: "
110 #define VI_PHEADER "X-vi-recover-path: "
112 static int rcv_copy __P((SCR *, int, char *));
113 static void rcv_email __P((SCR *, char *));
114 static char *rcv_gets __P((char *, size_t, int));
115 static int rcv_mailfile __P((SCR *, int, char *));
116 static int rcv_mktemp __P((SCR *, char *, char *, int));
119 * rcv_tmp --
120 * Build a file name that will be used as the recovery file.
122 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
125 rcv_tmp(SCR *sp, EXF *ep, char *name)
127 struct stat sb;
128 int fd;
129 char *dp, path[MAXPATHLEN];
132 * !!!
133 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
136 * If the recovery directory doesn't exist, try and create it. As
137 * the recovery files are themselves protected from reading/writing
138 * by other than the owner, the worst that can happen is that a user
139 * would have permission to remove other user's recovery files. If
140 * the sticky bit has the BSD semantics, that too will be impossible.
142 if (opts_empty(sp, O_RECDIR, 0))
143 goto err;
144 dp = O_STR(sp, O_RECDIR);
145 if (stat(dp, &sb)) {
146 if (errno != ENOENT || mkdir(dp, 0)) {
147 msgq(sp, M_SYSERR, "%s", dp);
148 goto err;
150 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
153 /* Newlines delimit the mail messages. */
154 if (strchr(name, '\n')) {
155 msgq(sp, M_ERR,
156 "055|Files with newlines in the name are unrecoverable");
157 goto err;
160 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
161 if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
162 goto err;
163 (void)close(fd);
165 if ((ep->rcv_path = strdup(path)) == NULL) {
166 msgq(sp, M_SYSERR, NULL);
167 (void)unlink(path);
168 err: msgq(sp, M_ERR,
169 "056|Modifications not recoverable if the session fails");
170 return (1);
173 /* We believe the file is recoverable. */
174 F_SET(ep, F_RCV_ON);
175 return (0);
179 * rcv_init --
180 * Force the file to be snapshotted for recovery.
182 * PUBLIC: int rcv_init __P((SCR *));
185 rcv_init(SCR *sp)
187 EXF *ep;
188 db_recno_t lno;
190 ep = sp->ep;
192 /* Only do this once. */
193 F_CLR(ep, F_FIRSTMODIFY);
195 /* If we already know the file isn't recoverable, we're done. */
196 if (!F_ISSET(ep, F_RCV_ON))
197 return (0);
199 /* Turn off recoverability until we figure out if this will work. */
200 F_CLR(ep, F_RCV_ON);
202 /* Test if we're recovering a file, not editing one. */
203 if (ep->rcv_mpath == NULL) {
204 /* Build a file to mail to the user. */
205 if (rcv_mailfile(sp, 0, NULL))
206 goto err;
208 /* Force a read of the entire file. */
209 if (db_last(sp, &lno))
210 goto err;
212 /* Turn on a busy message, and sync it to backing store. */
213 sp->gp->scr_busy(sp,
214 "057|Copying file for recovery...", BUSY_ON);
215 if (ep->db->sync(ep->db, 0)) {
216 msgq_str(sp, M_SYSERR, ep->rcv_path,
217 "058|Preservation failed: %s");
218 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
219 goto err;
221 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
224 /* Turn off the owner execute bit. */
225 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
227 /* We believe the file is recoverable. */
228 F_SET(ep, F_RCV_ON);
229 return (0);
231 err: msgq(sp, M_ERR,
232 "059|Modifications not recoverable if the session fails");
233 return (1);
237 * rcv_sync --
238 * Sync the file, optionally:
239 * flagging the backup file to be preserved
240 * snapshotting the backup file and send email to the user
241 * sending email to the user if the file was modified
242 * ending the file session
244 * PUBLIC: int rcv_sync __P((SCR *, u_int));
247 rcv_sync(SCR *sp, u_int flags)
249 EXF *ep;
250 int fd, rval;
251 char *dp, buf[1024];
253 /* Make sure that there's something to recover/sync. */
254 ep = sp->ep;
255 if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
256 return (0);
258 /* Sync the file if it's been modified. */
259 if (F_ISSET(ep, F_MODIFIED)) {
260 if (ep->db->sync(ep->db, 0)) {
261 F_CLR(ep, F_RCV_ON | F_RCV_NORM);
262 msgq_str(sp, M_SYSERR,
263 ep->rcv_path, "060|File backup failed: %s");
264 return (1);
267 /* REQUEST: don't remove backing file on exit. */
268 if (LF_ISSET(RCV_PRESERVE))
269 F_SET(ep, F_RCV_NORM);
271 /* REQUEST: send email. */
272 if (LF_ISSET(RCV_EMAIL))
273 rcv_email(sp, ep->rcv_mpath);
277 * !!!
278 * Each time the user exec's :preserve, we have to snapshot all of
279 * the recovery information, i.e. it's like the user re-edited the
280 * file. We copy the DB(3) backing file, and then create a new mail
281 * recovery file, it's simpler than exiting and reopening all of the
282 * underlying files.
284 * REQUEST: snapshot the file.
286 rval = 0;
287 if (LF_ISSET(RCV_SNAPSHOT)) {
288 if (opts_empty(sp, O_RECDIR, 0))
289 goto err;
290 dp = O_STR(sp, O_RECDIR);
291 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
292 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
293 goto err;
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;
302 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
304 if (0) {
305 err: rval = 1;
308 /* REQUEST: end the file session. */
309 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
310 rval = 1;
312 return (rval);
316 * rcv_mailfile --
317 * Build the file to mail to the user.
319 static int
320 rcv_mailfile(SCR *sp, int issync, char *cp_path)
322 EXF *ep;
323 GS *gp;
324 struct passwd *pw;
325 size_t len;
326 time_t now;
327 uid_t uid;
328 int fd;
329 char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
330 char *t1, *t2, *t3;
333 * XXX
334 * MAXHOSTNAMELEN is in various places on various systems, including
335 * <netdb.h> and <sys/socket.h>. If not found, use a large default.
337 #ifndef MAXHOSTNAMELEN
338 #define MAXHOSTNAMELEN 1024
339 #endif
340 char host[MAXHOSTNAMELEN];
342 gp = sp->gp;
343 if ((pw = getpwuid(uid = getuid())) == NULL) {
344 msgq(sp, M_ERR,
345 "062|Information on user id %u not found", uid);
346 return (1);
349 if (opts_empty(sp, O_RECDIR, 0))
350 return (1);
351 dp = O_STR(sp, O_RECDIR);
352 (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
353 if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
354 return (1);
357 * XXX
358 * We keep an open lock on the file so that the recover option can
359 * distinguish between files that are live and those that need to
360 * be recovered. There's an obvious window between the mkstemp call
361 * and the lock, but it's pretty small.
363 ep = sp->ep;
364 if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
365 msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
366 if (!issync) {
367 /* Save the recover file descriptor, and mail path. */
368 ep->rcv_fd = fd;
369 if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
370 msgq(sp, M_SYSERR, NULL);
371 goto err;
373 cp_path = ep->rcv_path;
377 * XXX
378 * We can't use stdio(3) here. The problem is that we may be using
379 * fcntl(2), so if ANY file descriptor into the file is closed, the
380 * lock is lost. So, we could never close the FILE *, even if we
381 * dup'd the fd first.
383 t = sp->frp->name;
384 if ((p = strrchr(t, '/')) == NULL)
385 p = t;
386 else
387 ++p;
388 (void)time(&now);
389 (void)gethostname(host, sizeof(host));
390 len = snprintf(buf, sizeof(buf),
391 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
392 VI_FHEADER, t, /* Non-standard. */
393 VI_PHEADER, cp_path, /* Non-standard. */
394 "Reply-To: root",
395 "From: root (Nvi recovery program)",
396 "To: ", pw->pw_name,
397 "Subject: Nvi saved the file ", p,
398 "Precedence: bulk"); /* For vacation(1). */
399 if (len > sizeof(buf) - 1)
400 goto lerr;
401 if (write(fd, buf, len) != len)
402 goto werr;
404 len = snprintf(buf, sizeof(buf),
405 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
406 "On ", ctime(&now), ", the user ", pw->pw_name,
407 " was editing a file named ", t, " on the machine ",
408 host, ", when it was saved for recovery. ",
409 "You can recover most, if not all, of the changes ",
410 "to this file using the -r option to ", gp->progname, ":\n\n\t",
411 gp->progname, " -r ", t);
412 if (len > sizeof(buf) - 1) {
413 lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun");
414 goto err;
418 * Format the message. (Yes, I know it's silly.)
419 * Requires that the message end in a <newline>.
421 #define FMTCOLS 60
422 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
423 /* Check for a short length. */
424 if (len <= FMTCOLS) {
425 t2 = t1 + (len - 1);
426 goto wout;
429 /* Check for a required <newline>. */
430 t2 = strchr(t1, '\n');
431 if (t2 - t1 <= FMTCOLS)
432 goto wout;
434 /* Find the closest space, if any. */
435 for (t3 = t2; t2 > t1; --t2)
436 if (*t2 == ' ') {
437 if (t2 - t1 <= FMTCOLS)
438 goto wout;
439 t3 = t2;
441 t2 = t3;
443 /* t2 points to the last character to display. */
444 wout: *t2++ = '\n';
446 /* t2 points one after the last character to display. */
447 if (write(fd, t1, t2 - t1) != t2 - t1)
448 goto werr;
451 if (issync) {
452 rcv_email(sp, mpath);
453 if (close(fd)) {
454 werr: msgq(sp, M_SYSERR, "065|Recovery file");
455 goto err;
458 return (0);
460 err: if (!issync)
461 ep->rcv_fd = -1;
462 if (fd != -1)
463 (void)close(fd);
464 return (1);
468 * people making love
469 * never exactly the same
470 * just like a snowflake
472 * rcv_list --
473 * List the files that can be recovered by this user.
475 * PUBLIC: int rcv_list __P((SCR *));
478 rcv_list(SCR *sp)
480 struct dirent *dp;
481 struct stat sb;
482 DIR *dirp;
483 FILE *fp;
484 int found;
485 char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
487 /* Open the recovery directory for reading. */
488 if (opts_empty(sp, O_RECDIR, 0))
489 return (1);
490 p = O_STR(sp, O_RECDIR);
491 if (chdir(p) || (dirp = opendir(".")) == NULL) {
492 msgq_str(sp, M_SYSERR, p, "recdir: %s");
493 return (1);
496 /* Read the directory. */
497 for (found = 0; (dp = readdir(dirp)) != NULL;) {
498 if (strncmp(dp->d_name, "recover.", 8))
499 continue;
502 * If it's readable, it's recoverable.
504 * XXX
505 * Should be "r", we don't want to write the file. However,
506 * if we're using fcntl(2), there's no way to lock a file
507 * descriptor that's not open for writing.
509 if ((fp = fopen(dp->d_name, "r+")) == NULL)
510 continue;
512 switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
513 case LOCK_FAILED:
515 * XXX
516 * Assume that a lock can't be acquired, but that we
517 * should permit recovery anyway. If this is wrong,
518 * and someone else is using the file, we're going to
519 * die horribly.
521 break;
522 case LOCK_SUCCESS:
523 break;
524 case LOCK_UNAVAIL:
525 /* If it's locked, it's live. */
526 (void)fclose(fp);
527 continue;
530 /* Check the headers. */
531 if (fgets(file, sizeof(file), fp) == NULL ||
532 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
533 (p = strchr(file, '\n')) == NULL ||
534 fgets(path, sizeof(path), fp) == NULL ||
535 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
536 (t = strchr(path, '\n')) == NULL) {
537 msgq_str(sp, M_ERR, dp->d_name,
538 "066|%s: malformed recovery file");
539 goto next;
541 *p = *t = '\0';
544 * If the file doesn't exist, it's an orphaned recovery file,
545 * toss it.
547 * XXX
548 * This can occur if the backup file was deleted and we crashed
549 * before deleting the email file.
551 errno = 0;
552 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
553 errno == ENOENT) {
554 (void)unlink(dp->d_name);
555 goto next;
558 /* Get the last modification time and display. */
559 (void)fstat(fileno(fp), &sb);
560 (void)printf("%.24s: %s\n",
561 ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
562 found = 1;
564 /* Close, discarding lock. */
565 next: (void)fclose(fp);
567 if (found == 0)
568 (void)printf("%s: No files to recover\n", sp->gp->progname);
569 (void)closedir(dirp);
570 return (0);
574 * rcv_read --
575 * Start a recovered file as the file to edit.
577 * PUBLIC: int rcv_read __P((SCR *, FREF *));
580 rcv_read(SCR *sp, FREF *frp)
582 struct dirent *dp;
583 struct stat sb;
584 DIR *dirp;
585 EXF *ep;
586 time_t rec_mtime;
587 int fd, found, locked, requested, sv_fd;
588 char *name, *p, *t, *rp, *recp, *pathp;
589 char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
591 if (opts_empty(sp, O_RECDIR, 0))
592 return (1);
593 rp = O_STR(sp, O_RECDIR);
594 if ((dirp = opendir(rp)) == NULL) {
595 msgq_str(sp, M_ERR, rp, "%s");
596 return (1);
599 name = frp->name;
600 sv_fd = -1;
601 rec_mtime = 0;
602 recp = pathp = NULL;
603 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
604 if (strncmp(dp->d_name, "recover.", 8))
605 continue;
606 (void)snprintf(recpath,
607 sizeof(recpath), "%s/%s", rp, dp->d_name);
610 * If it's readable, it's recoverable. It would be very
611 * nice to use stdio(3), but, we can't because that would
612 * require closing and then reopening the file so that we
613 * could have a lock and still close the FP. Another tip
614 * of the hat to fcntl(2).
616 * XXX
617 * Should be O_RDONLY, we don't want to write it. However,
618 * if we're using fcntl(2), there's no way to lock a file
619 * descriptor that's not open for writing.
621 if ((fd = open(recpath, O_RDWR, 0)) == -1)
622 continue;
624 switch (file_lock(sp, NULL, NULL, fd, 1)) {
625 case LOCK_FAILED:
627 * XXX
628 * Assume that a lock can't be acquired, but that we
629 * should permit recovery anyway. If this is wrong,
630 * and someone else is using the file, we're going to
631 * die horribly.
633 locked = 0;
634 break;
635 case LOCK_SUCCESS:
636 locked = 1;
637 break;
638 case LOCK_UNAVAIL:
639 /* If it's locked, it's live. */
640 (void)close(fd);
641 continue;
644 /* Check the headers. */
645 if (rcv_gets(file, sizeof(file), fd) == NULL ||
646 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
647 (p = strchr(file, '\n')) == NULL ||
648 rcv_gets(path, sizeof(path), fd) == NULL ||
649 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
650 (t = strchr(path, '\n')) == NULL) {
651 msgq_str(sp, M_ERR, recpath,
652 "067|%s: malformed recovery file");
653 goto next;
655 *p = *t = '\0';
656 ++found;
659 * If the file doesn't exist, it's an orphaned recovery file,
660 * toss it.
662 * XXX
663 * This can occur if the backup file was deleted and we crashed
664 * before deleting the email file.
666 errno = 0;
667 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
668 errno == ENOENT) {
669 (void)unlink(dp->d_name);
670 goto next;
673 /* Check the file name. */
674 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
675 goto next;
677 ++requested;
680 * If we've found more than one, take the most recent.
682 * XXX
683 * Since we're using st_mtime, for portability reasons,
684 * we only get a single second granularity, instead of
685 * getting it right.
687 (void)fstat(fd, &sb);
688 if (recp == NULL || rec_mtime < sb.st_mtime) {
689 p = recp;
690 t = pathp;
691 if ((recp = strdup(recpath)) == NULL) {
692 msgq(sp, M_SYSERR, NULL);
693 recp = p;
694 goto next;
696 if ((pathp = strdup(path)) == NULL) {
697 msgq(sp, M_SYSERR, NULL);
698 free(recp);
699 recp = p;
700 pathp = t;
701 goto next;
703 if (p != NULL) {
704 free(p);
705 free(t);
707 rec_mtime = sb.st_mtime;
708 if (sv_fd != -1)
709 (void)close(sv_fd);
710 sv_fd = fd;
711 } else
712 next: (void)close(fd);
714 (void)closedir(dirp);
716 if (recp == NULL) {
717 msgq_str(sp, M_INFO, name,
718 "068|No files named %s, readable by you, to recover");
719 return (1);
721 if (found) {
722 if (requested > 1)
723 msgq(sp, M_INFO,
724 "069|There are older versions of this file for you to recover");
725 if (found > requested)
726 msgq(sp, M_INFO,
727 "070|There are other files for you to recover");
731 * Create the FREF structure, start the btree file.
733 * XXX
734 * file_init() is going to set ep->rcv_path.
736 if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
737 free(recp);
738 free(pathp);
739 (void)close(sv_fd);
740 return (1);
744 * We keep an open lock on the file so that the recover option can
745 * distinguish between files that are live and those that need to
746 * be recovered. The lock is already acquired, just copy it.
748 ep = sp->ep;
749 ep->rcv_mpath = recp;
750 ep->rcv_fd = sv_fd;
751 if (!locked)
752 F_SET(frp, FR_UNLOCKED);
754 /* We believe the file is recoverable. */
755 F_SET(ep, F_RCV_ON);
756 return (0);
760 * rcv_copy --
761 * Copy a recovery file.
763 static int
764 rcv_copy(SCR *sp, int wfd, char *fname)
766 int nr, nw, off, rfd;
767 char buf[8 * 1024];
769 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
770 goto err;
771 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
772 for (off = 0; nr; nr -= nw, off += nw)
773 if ((nw = write(wfd, buf + off, nr)) < 0)
774 goto err;
775 if (nr == 0)
776 return (0);
778 err: msgq_str(sp, M_SYSERR, fname, "%s");
779 return (1);
783 * rcv_gets --
784 * Fgets(3) for a file descriptor.
786 static char *
787 rcv_gets(char *buf, size_t len, int fd)
789 int nr;
790 char *p;
792 if ((nr = read(fd, buf, len - 1)) == -1)
793 return (NULL);
794 if ((p = strchr(buf, '\n')) == NULL)
795 return (NULL);
796 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
797 return (buf);
801 * rcv_mktemp --
802 * Paranoid make temporary file routine.
804 static int
805 rcv_mktemp(SCR *sp, char *path, char *dname, int perms)
807 int fd;
810 * !!!
811 * We expect mkstemp(3) to set the permissions correctly. On
812 * historic System V systems, mkstemp didn't. Do it here, on
813 * GP's.
815 * XXX
816 * The variable perms should really be a mode_t, and it would
817 * be nice to use fchmod(2) instead of chmod(2), here.
819 if ((fd = mkstemp(path)) == -1)
820 msgq_str(sp, M_SYSERR, dname, "%s");
821 else
822 (void)chmod(path, perms);
823 return (fd);
827 * rcv_email --
828 * Send email.
830 static void
831 rcv_email(SCR *sp, char *fname)
833 struct stat sb;
834 char buf[MAXPATHLEN * 2 + 20];
836 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
837 msgq_str(sp, M_SYSERR,
838 _PATH_SENDMAIL, "071|not sending email: %s");
839 else {
841 * !!!
842 * If you need to port this to a system that doesn't have
843 * sendmail, the -t flag causes sendmail to read the message
844 * for the recipients instead of specifying them some other
845 * way.
847 (void)snprintf(buf, sizeof(buf),
848 "%s -t < %s", _PATH_SENDMAIL, fname);
849 (void)system(buf);