use strchr instead of doing it manually
[nvi.git] / common / recover.c
blob96056b813bf41a60cdc8da6117cad6352a753e42
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.28 2001/11/01 09:56:58 skimo Exp $ (Berkeley) $Date: 2001/11/01 09:56:58 $";
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, *p, 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(p, '\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 DBTYPE type;
206 /* Build a file to mail to the user. */
207 if (rcv_mailfile(sp, 0, NULL))
208 goto err;
210 /* Force a read of the entire file. */
211 if (db_last(sp, &lno))
212 goto err;
214 /* Turn on a busy message, and sync it to backing store. */
215 sp->gp->scr_busy(sp,
216 "057|Copying file for recovery...", BUSY_ON);
217 type = ep->db->type;
218 /* XXXX gross hack
219 We don't want DB to write to the underlying
220 recno database, so we just tell it that it's
221 not a recno database
223 ep->db->type = DB_UNKNOWN;
224 if (ep->db->sync(ep->db, 0)) {
225 msgq_str(sp, M_SYSERR, ep->rcv_path,
226 "058|Preservation failed: %s");
227 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
228 goto err;
230 ep->db->type = type;
231 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
234 /* Turn off the owner execute bit. */
235 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
237 /* We believe the file is recoverable. */
238 F_SET(ep, F_RCV_ON);
239 return (0);
241 err: msgq(sp, M_ERR,
242 "059|Modifications not recoverable if the session fails");
243 return (1);
247 * rcv_sync --
248 * Sync the file, optionally:
249 * flagging the backup file to be preserved
250 * snapshotting the backup file and send email to the user
251 * sending email to the user if the file was modified
252 * ending the file session
254 * PUBLIC: int rcv_sync __P((SCR *, u_int));
257 rcv_sync(SCR *sp, u_int flags)
259 EXF *ep;
260 int fd, rval;
261 char *dp, buf[1024];
263 /* Make sure that there's something to recover/sync. */
264 ep = sp->ep;
265 if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
266 return (0);
268 /* Sync the file if it's been modified. */
269 if (F_ISSET(ep, F_MODIFIED)) {
270 DBTYPE type;
271 type = ep->db->type;
272 /* XXXX gross hack
273 We don't want DB to write to the underlying
274 recno database, so we just tell it that it's
275 not a recno database
277 ep->db->type = DB_UNKNOWN;
278 if (ep->db->sync(ep->db, 0)) {
279 F_CLR(ep, F_RCV_ON | F_RCV_NORM);
280 msgq_str(sp, M_SYSERR,
281 ep->rcv_path, "060|File backup failed: %s");
282 return (1);
284 ep->db->type = type;
286 /* REQUEST: don't remove backing file on exit. */
287 if (LF_ISSET(RCV_PRESERVE))
288 F_SET(ep, F_RCV_NORM);
290 /* REQUEST: send email. */
291 if (LF_ISSET(RCV_EMAIL))
292 rcv_email(sp, ep->rcv_mpath);
296 * !!!
297 * Each time the user exec's :preserve, we have to snapshot all of
298 * the recovery information, i.e. it's like the user re-edited the
299 * file. We copy the DB(3) backing file, and then create a new mail
300 * recovery file, it's simpler than exiting and reopening all of the
301 * underlying files.
303 * REQUEST: snapshot the file.
305 rval = 0;
306 if (LF_ISSET(RCV_SNAPSHOT)) {
307 if (opts_empty(sp, O_RECDIR, 0))
308 goto err;
309 dp = O_STR(sp, O_RECDIR);
310 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
311 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
312 goto err;
313 sp->gp->scr_busy(sp,
314 "061|Copying file for recovery...", BUSY_ON);
315 if (rcv_copy(sp, fd, ep->rcv_path) ||
316 close(fd) || rcv_mailfile(sp, 1, buf)) {
317 (void)unlink(buf);
318 (void)close(fd);
319 rval = 1;
321 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
323 if (0) {
324 err: rval = 1;
327 /* REQUEST: end the file session. */
328 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
329 rval = 1;
331 return (rval);
335 * rcv_mailfile --
336 * Build the file to mail to the user.
338 static int
339 rcv_mailfile(SCR *sp, int issync, char *cp_path)
341 EXF *ep;
342 GS *gp;
343 struct passwd *pw;
344 size_t len;
345 time_t now;
346 uid_t uid;
347 int fd;
348 char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
349 char *t1, *t2, *t3;
352 * XXX
353 * MAXHOSTNAMELEN is in various places on various systems, including
354 * <netdb.h> and <sys/socket.h>. If not found, use a large default.
356 #ifndef MAXHOSTNAMELEN
357 #define MAXHOSTNAMELEN 1024
358 #endif
359 char host[MAXHOSTNAMELEN];
361 gp = sp->gp;
362 if ((pw = getpwuid(uid = getuid())) == NULL) {
363 msgq(sp, M_ERR,
364 "062|Information on user id %u not found", uid);
365 return (1);
368 if (opts_empty(sp, O_RECDIR, 0))
369 return (1);
370 dp = O_STR(sp, O_RECDIR);
371 (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
372 if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
373 return (1);
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.
382 ep = sp->ep;
383 if (file_lock(sp, NULL, 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 = fd;
388 if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
389 msgq(sp, M_SYSERR, NULL);
390 goto err;
392 cp_path = ep->rcv_path;
396 * XXX
397 * We can't use stdio(3) here. The problem is that we may be using
398 * fcntl(2), so if ANY file descriptor into the file is closed, the
399 * lock is lost. So, we could never close the FILE *, even if we
400 * dup'd the fd first.
402 t = sp->frp->name;
403 if ((p = strrchr(t, '/')) == NULL)
404 p = t;
405 else
406 ++p;
407 (void)time(&now);
408 (void)gethostname(host, sizeof(host));
409 len = snprintf(buf, sizeof(buf),
410 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
411 VI_FHEADER, t, /* Non-standard. */
412 VI_PHEADER, cp_path, /* Non-standard. */
413 "Reply-To: root",
414 "From: root (Nvi recovery program)",
415 "To: ", pw->pw_name,
416 "Subject: Nvi saved the file ", p,
417 "Precedence: bulk"); /* For vacation(1). */
418 if (len > sizeof(buf) - 1)
419 goto lerr;
420 if (write(fd, buf, len) != len)
421 goto werr;
423 len = snprintf(buf, sizeof(buf),
424 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
425 "On ", ctime(&now), ", the user ", pw->pw_name,
426 " was editing a file named ", t, " on the machine ",
427 host, ", when it was saved for recovery. ",
428 "You can recover most, if not all, of the changes ",
429 "to this file using the -r option to ", gp->progname, ":\n\n\t",
430 gp->progname, " -r ", t);
431 if (len > sizeof(buf) - 1) {
432 lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun");
433 goto err;
437 * Format the message. (Yes, I know it's silly.)
438 * Requires that the message end in a <newline>.
440 #define FMTCOLS 60
441 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
442 /* Check for a short length. */
443 if (len <= FMTCOLS) {
444 t2 = t1 + (len - 1);
445 goto wout;
448 /* Check for a required <newline>. */
449 t2 = strchr(t1, '\n');
450 if (t2 - t1 <= FMTCOLS)
451 goto wout;
453 /* Find the closest space, if any. */
454 for (t3 = t2; t2 > t1; --t2)
455 if (*t2 == ' ') {
456 if (t2 - t1 <= FMTCOLS)
457 goto wout;
458 t3 = t2;
460 t2 = t3;
462 /* t2 points to the last character to display. */
463 wout: *t2++ = '\n';
465 /* t2 points one after the last character to display. */
466 if (write(fd, t1, t2 - t1) != t2 - t1)
467 goto werr;
470 if (issync) {
471 rcv_email(sp, mpath);
472 if (close(fd)) {
473 werr: msgq(sp, M_SYSERR, "065|Recovery file");
474 goto err;
477 return (0);
479 err: if (!issync)
480 ep->rcv_fd = -1;
481 if (fd != -1)
482 (void)close(fd);
483 return (1);
487 * people making love
488 * never exactly the same
489 * just like a snowflake
491 * rcv_list --
492 * List the files that can be recovered by this user.
494 * PUBLIC: int rcv_list __P((SCR *));
497 rcv_list(SCR *sp)
499 struct dirent *dp;
500 struct stat sb;
501 DIR *dirp;
502 FILE *fp;
503 int found;
504 char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
506 /* Open the recovery directory for reading. */
507 if (opts_empty(sp, O_RECDIR, 0))
508 return (1);
509 p = O_STR(sp, O_RECDIR);
510 if (chdir(p) || (dirp = opendir(".")) == NULL) {
511 msgq_str(sp, M_SYSERR, p, "recdir: %s");
512 return (1);
515 /* Read the directory. */
516 for (found = 0; (dp = readdir(dirp)) != NULL;) {
517 if (strncmp(dp->d_name, "recover.", 8))
518 continue;
521 * If it's readable, it's recoverable.
523 * XXX
524 * Should be "r", we don't want to write the file. However,
525 * if we're using fcntl(2), there's no way to lock a file
526 * descriptor that's not open for writing.
528 if ((fp = fopen(dp->d_name, "r+")) == NULL)
529 continue;
531 switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
532 case LOCK_FAILED:
534 * XXX
535 * Assume that a lock can't be acquired, but that we
536 * should permit recovery anyway. If this is wrong,
537 * and someone else is using the file, we're going to
538 * die horribly.
540 break;
541 case LOCK_SUCCESS:
542 break;
543 case LOCK_UNAVAIL:
544 /* If it's locked, it's live. */
545 (void)fclose(fp);
546 continue;
549 /* Check the headers. */
550 if (fgets(file, sizeof(file), fp) == NULL ||
551 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
552 (p = strchr(file, '\n')) == NULL ||
553 fgets(path, sizeof(path), fp) == NULL ||
554 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
555 (t = strchr(path, '\n')) == NULL) {
556 msgq_str(sp, M_ERR, dp->d_name,
557 "066|%s: malformed recovery file");
558 goto next;
560 *p = *t = '\0';
563 * If the file doesn't exist, it's an orphaned recovery file,
564 * toss it.
566 * XXX
567 * This can occur if the backup file was deleted and we crashed
568 * before deleting the email file.
570 errno = 0;
571 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
572 errno == ENOENT) {
573 (void)unlink(dp->d_name);
574 goto next;
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 + sizeof(VI_FHEADER) - 1);
581 found = 1;
583 /* Close, discarding lock. */
584 next: (void)fclose(fp);
586 if (found == 0)
587 (void)printf("vi: no files to recover.\n");
588 (void)closedir(dirp);
589 return (0);
593 * rcv_read --
594 * Start a recovered file as the file to edit.
596 * PUBLIC: int rcv_read __P((SCR *, FREF *));
599 rcv_read(SCR *sp, FREF *frp)
601 struct dirent *dp;
602 struct stat sb;
603 DIR *dirp;
604 EXF *ep;
605 time_t rec_mtime;
606 int fd, found, locked, requested, sv_fd;
607 char *name, *p, *t, *rp, *recp, *pathp;
608 char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
610 if (opts_empty(sp, O_RECDIR, 0))
611 return (1);
612 rp = O_STR(sp, O_RECDIR);
613 if ((dirp = opendir(rp)) == NULL) {
614 msgq_str(sp, M_ERR, rp, "%s");
615 return (1);
618 name = frp->name;
619 sv_fd = -1;
620 rec_mtime = 0;
621 recp = pathp = NULL;
622 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
623 if (strncmp(dp->d_name, "recover.", 8))
624 continue;
625 (void)snprintf(recpath,
626 sizeof(recpath), "%s/%s", rp, dp->d_name);
629 * If it's readable, it's recoverable. It would be very
630 * nice to use stdio(3), but, we can't because that would
631 * require closing and then reopening the file so that we
632 * could have a lock and still close the FP. Another tip
633 * of the hat to fcntl(2).
635 * XXX
636 * Should be O_RDONLY, we don't want to write it. However,
637 * if we're using fcntl(2), there's no way to lock a file
638 * descriptor that's not open for writing.
640 if ((fd = open(recpath, O_RDWR, 0)) == -1)
641 continue;
643 switch (file_lock(sp, NULL, NULL, fd, 1)) {
644 case LOCK_FAILED:
646 * XXX
647 * Assume that a lock can't be acquired, but that we
648 * should permit recovery anyway. If this is wrong,
649 * and someone else is using the file, we're going to
650 * die horribly.
652 locked = 0;
653 break;
654 case LOCK_SUCCESS:
655 locked = 1;
656 break;
657 case LOCK_UNAVAIL:
658 /* If it's locked, it's live. */
659 (void)close(fd);
660 continue;
663 /* Check the headers. */
664 if (rcv_gets(file, sizeof(file), fd) == NULL ||
665 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
666 (p = strchr(file, '\n')) == NULL ||
667 rcv_gets(path, sizeof(path), fd) == NULL ||
668 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
669 (t = strchr(path, '\n')) == NULL) {
670 msgq_str(sp, M_ERR, recpath,
671 "067|%s: malformed recovery file");
672 goto next;
674 *p = *t = '\0';
675 ++found;
678 * If the file doesn't exist, it's an orphaned recovery file,
679 * toss it.
681 * XXX
682 * This can occur if the backup file was deleted and we crashed
683 * before deleting the email file.
685 errno = 0;
686 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
687 errno == ENOENT) {
688 (void)unlink(dp->d_name);
689 goto next;
692 /* Check the file name. */
693 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
694 goto next;
696 ++requested;
699 * If we've found more than one, take the most recent.
701 * XXX
702 * Since we're using st_mtime, for portability reasons,
703 * we only get a single second granularity, instead of
704 * getting it right.
706 (void)fstat(fd, &sb);
707 if (recp == NULL || rec_mtime < sb.st_mtime) {
708 p = recp;
709 t = pathp;
710 if ((recp = strdup(recpath)) == NULL) {
711 msgq(sp, M_SYSERR, NULL);
712 recp = p;
713 goto next;
715 if ((pathp = strdup(path)) == NULL) {
716 msgq(sp, M_SYSERR, NULL);
717 free(recp);
718 recp = p;
719 pathp = t;
720 goto next;
722 if (p != NULL) {
723 free(p);
724 free(t);
726 rec_mtime = sb.st_mtime;
727 if (sv_fd != -1)
728 (void)close(sv_fd);
729 sv_fd = fd;
730 } else
731 next: (void)close(fd);
733 (void)closedir(dirp);
735 if (recp == NULL) {
736 msgq_str(sp, M_INFO, name,
737 "068|No files named %s, readable by you, to recover");
738 return (1);
740 if (found) {
741 if (requested > 1)
742 msgq(sp, M_INFO,
743 "069|There are older versions of this file for you to recover");
744 if (found > requested)
745 msgq(sp, M_INFO,
746 "070|There are other files for you to recover");
750 * Create the FREF structure, start the btree file.
752 * XXX
753 * file_init() is going to set ep->rcv_path.
755 if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
756 free(recp);
757 free(pathp);
758 (void)close(sv_fd);
759 return (1);
763 * We keep an open lock on the file so that the recover option can
764 * distinguish between files that are live and those that need to
765 * be recovered. The lock is already acquired, just copy it.
767 ep = sp->ep;
768 ep->rcv_mpath = recp;
769 ep->rcv_fd = sv_fd;
770 if (!locked)
771 F_SET(frp, FR_UNLOCKED);
773 /* We believe the file is recoverable. */
774 F_SET(ep, F_RCV_ON);
775 return (0);
779 * rcv_copy --
780 * Copy a recovery file.
782 static int
783 rcv_copy(SCR *sp, int wfd, char *fname)
785 int nr, nw, off, rfd;
786 char buf[8 * 1024];
788 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
789 goto err;
790 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
791 for (off = 0; nr; nr -= nw, off += nw)
792 if ((nw = write(wfd, buf + off, nr)) < 0)
793 goto err;
794 if (nr == 0)
795 return (0);
797 err: msgq_str(sp, M_SYSERR, fname, "%s");
798 return (1);
802 * rcv_gets --
803 * Fgets(3) for a file descriptor.
805 static char *
806 rcv_gets(char *buf, size_t len, int fd)
808 int nr;
809 char *p;
811 if ((nr = read(fd, buf, len - 1)) == -1)
812 return (NULL);
813 if ((p = strchr(buf, '\n')) == NULL)
814 return (NULL);
815 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
816 return (buf);
820 * rcv_mktemp --
821 * Paranoid make temporary file routine.
823 static int
824 rcv_mktemp(SCR *sp, char *path, char *dname, int perms)
826 int fd;
829 * !!!
830 * We expect mkstemp(3) to set the permissions correctly. On
831 * historic System V systems, mkstemp didn't. Do it here, on
832 * GP's.
834 * XXX
835 * The variable perms should really be a mode_t, and it would
836 * be nice to use fchmod(2) instead of chmod(2), here.
838 if ((fd = mkstemp(path)) == -1)
839 msgq_str(sp, M_SYSERR, dname, "%s");
840 else
841 (void)chmod(path, perms);
842 return (fd);
846 * rcv_email --
847 * Send email.
849 static void
850 rcv_email(SCR *sp, char *fname)
852 struct stat sb;
853 char buf[MAXPATHLEN * 2 + 20];
855 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
856 msgq_str(sp, M_SYSERR,
857 _PATH_SENDMAIL, "071|not sending email: %s");
858 else {
860 * !!!
861 * If you need to port this to a system that doesn't have
862 * sendmail, the -t flag causes sendmail to read the message
863 * for the recipients instead of specifying them some other
864 * way.
866 (void)snprintf(buf, sizeof(buf),
867 "%s -t < %s", _PATH_SENDMAIL, fname);
868 (void)system(buf);