Copy type field directly instead of calling get_type to avoid
[nvi.git] / common / recover.c
blob117121a85b94c00346f68d4c35fa7c99f45f274e
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.27 2001/08/21 11:52:04 skimo Exp $ (Berkeley) $Date: 2001/08/21 11:52:04 $";
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 for (p = name; *p; ++p)
155 if (*p == '\n') {
156 msgq(sp, M_ERR,
157 "055|Files with newlines in the name are unrecoverable");
158 goto err;
161 (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
162 if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
163 goto err;
164 (void)close(fd);
166 if ((ep->rcv_path = strdup(path)) == NULL) {
167 msgq(sp, M_SYSERR, NULL);
168 (void)unlink(path);
169 err: msgq(sp, M_ERR,
170 "056|Modifications not recoverable if the session fails");
171 return (1);
174 /* We believe the file is recoverable. */
175 F_SET(ep, F_RCV_ON);
176 return (0);
180 * rcv_init --
181 * Force the file to be snapshotted for recovery.
183 * PUBLIC: int rcv_init __P((SCR *));
186 rcv_init(SCR *sp)
188 EXF *ep;
189 db_recno_t lno;
191 ep = sp->ep;
193 /* Only do this once. */
194 F_CLR(ep, F_FIRSTMODIFY);
196 /* If we already know the file isn't recoverable, we're done. */
197 if (!F_ISSET(ep, F_RCV_ON))
198 return (0);
200 /* Turn off recoverability until we figure out if this will work. */
201 F_CLR(ep, F_RCV_ON);
203 /* Test if we're recovering a file, not editing one. */
204 if (ep->rcv_mpath == NULL) {
205 DBTYPE type;
207 /* Build a file to mail to the user. */
208 if (rcv_mailfile(sp, 0, NULL))
209 goto err;
211 /* Force a read of the entire file. */
212 if (db_last(sp, &lno))
213 goto err;
215 /* Turn on a busy message, and sync it to backing store. */
216 sp->gp->scr_busy(sp,
217 "057|Copying file for recovery...", BUSY_ON);
218 type = ep->db->type;
219 /* XXXX gross hack
220 We don't want DB to write to the underlying
221 recno database, so we just tell it that it's
222 not a recno database
224 ep->db->type = DB_UNKNOWN;
225 if (ep->db->sync(ep->db, 0)) {
226 msgq_str(sp, M_SYSERR, ep->rcv_path,
227 "058|Preservation failed: %s");
228 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
229 goto err;
231 ep->db->type = type;
232 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
235 /* Turn off the owner execute bit. */
236 (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
238 /* We believe the file is recoverable. */
239 F_SET(ep, F_RCV_ON);
240 return (0);
242 err: msgq(sp, M_ERR,
243 "059|Modifications not recoverable if the session fails");
244 return (1);
248 * rcv_sync --
249 * Sync the file, optionally:
250 * flagging the backup file to be preserved
251 * snapshotting the backup file and send email to the user
252 * sending email to the user if the file was modified
253 * ending the file session
255 * PUBLIC: int rcv_sync __P((SCR *, u_int));
258 rcv_sync(SCR *sp, u_int flags)
260 EXF *ep;
261 int fd, rval;
262 char *dp, buf[1024];
264 /* Make sure that there's something to recover/sync. */
265 ep = sp->ep;
266 if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
267 return (0);
269 /* Sync the file if it's been modified. */
270 if (F_ISSET(ep, F_MODIFIED)) {
271 DBTYPE type;
272 type = ep->db->type;
273 /* XXXX gross hack
274 We don't want DB to write to the underlying
275 recno database, so we just tell it that it's
276 not a recno database
278 ep->db->type = DB_UNKNOWN;
279 if (ep->db->sync(ep->db, 0)) {
280 F_CLR(ep, F_RCV_ON | F_RCV_NORM);
281 msgq_str(sp, M_SYSERR,
282 ep->rcv_path, "060|File backup failed: %s");
283 return (1);
285 ep->db->type = type;
287 /* REQUEST: don't remove backing file on exit. */
288 if (LF_ISSET(RCV_PRESERVE))
289 F_SET(ep, F_RCV_NORM);
291 /* REQUEST: send email. */
292 if (LF_ISSET(RCV_EMAIL))
293 rcv_email(sp, ep->rcv_mpath);
297 * !!!
298 * Each time the user exec's :preserve, we have to snapshot all of
299 * the recovery information, i.e. it's like the user re-edited the
300 * file. We copy the DB(3) backing file, and then create a new mail
301 * recovery file, it's simpler than exiting and reopening all of the
302 * underlying files.
304 * REQUEST: snapshot the file.
306 rval = 0;
307 if (LF_ISSET(RCV_SNAPSHOT)) {
308 if (opts_empty(sp, O_RECDIR, 0))
309 goto err;
310 dp = O_STR(sp, O_RECDIR);
311 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
312 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
313 goto err;
314 sp->gp->scr_busy(sp,
315 "061|Copying file for recovery...", BUSY_ON);
316 if (rcv_copy(sp, fd, ep->rcv_path) ||
317 close(fd) || rcv_mailfile(sp, 1, buf)) {
318 (void)unlink(buf);
319 (void)close(fd);
320 rval = 1;
322 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
324 if (0) {
325 err: rval = 1;
328 /* REQUEST: end the file session. */
329 if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
330 rval = 1;
332 return (rval);
336 * rcv_mailfile --
337 * Build the file to mail to the user.
339 static int
340 rcv_mailfile(SCR *sp, int issync, char *cp_path)
342 EXF *ep;
343 GS *gp;
344 struct passwd *pw;
345 size_t len;
346 time_t now;
347 uid_t uid;
348 int fd;
349 char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
350 char *t1, *t2, *t3;
353 * XXX
354 * MAXHOSTNAMELEN is in various places on various systems, including
355 * <netdb.h> and <sys/socket.h>. If not found, use a large default.
357 #ifndef MAXHOSTNAMELEN
358 #define MAXHOSTNAMELEN 1024
359 #endif
360 char host[MAXHOSTNAMELEN];
362 gp = sp->gp;
363 if ((pw = getpwuid(uid = getuid())) == NULL) {
364 msgq(sp, M_ERR,
365 "062|Information on user id %u not found", uid);
366 return (1);
369 if (opts_empty(sp, O_RECDIR, 0))
370 return (1);
371 dp = O_STR(sp, O_RECDIR);
372 (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
373 if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
374 return (1);
377 * XXX
378 * We keep an open lock on the file so that the recover option can
379 * distinguish between files that are live and those that need to
380 * be recovered. There's an obvious window between the mkstemp call
381 * and the lock, but it's pretty small.
383 ep = sp->ep;
384 if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
385 msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
386 if (!issync) {
387 /* Save the recover file descriptor, and mail path. */
388 ep->rcv_fd = fd;
389 if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
390 msgq(sp, M_SYSERR, NULL);
391 goto err;
393 cp_path = ep->rcv_path;
397 * XXX
398 * We can't use stdio(3) here. The problem is that we may be using
399 * fcntl(2), so if ANY file descriptor into the file is closed, the
400 * lock is lost. So, we could never close the FILE *, even if we
401 * dup'd the fd first.
403 t = sp->frp->name;
404 if ((p = strrchr(t, '/')) == NULL)
405 p = t;
406 else
407 ++p;
408 (void)time(&now);
409 (void)gethostname(host, sizeof(host));
410 len = snprintf(buf, sizeof(buf),
411 "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
412 VI_FHEADER, t, /* Non-standard. */
413 VI_PHEADER, cp_path, /* Non-standard. */
414 "Reply-To: root",
415 "From: root (Nvi recovery program)",
416 "To: ", pw->pw_name,
417 "Subject: Nvi saved the file ", p,
418 "Precedence: bulk"); /* For vacation(1). */
419 if (len > sizeof(buf) - 1)
420 goto lerr;
421 if (write(fd, buf, len) != len)
422 goto werr;
424 len = snprintf(buf, sizeof(buf),
425 "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
426 "On ", ctime(&now), ", the user ", pw->pw_name,
427 " was editing a file named ", t, " on the machine ",
428 host, ", when it was saved for recovery. ",
429 "You can recover most, if not all, of the changes ",
430 "to this file using the -r option to ", gp->progname, ":\n\n\t",
431 gp->progname, " -r ", t);
432 if (len > sizeof(buf) - 1) {
433 lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun");
434 goto err;
438 * Format the message. (Yes, I know it's silly.)
439 * Requires that the message end in a <newline>.
441 #define FMTCOLS 60
442 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
443 /* Check for a short length. */
444 if (len <= FMTCOLS) {
445 t2 = t1 + (len - 1);
446 goto wout;
449 /* Check for a required <newline>. */
450 t2 = strchr(t1, '\n');
451 if (t2 - t1 <= FMTCOLS)
452 goto wout;
454 /* Find the closest space, if any. */
455 for (t3 = t2; t2 > t1; --t2)
456 if (*t2 == ' ') {
457 if (t2 - t1 <= FMTCOLS)
458 goto wout;
459 t3 = t2;
461 t2 = t3;
463 /* t2 points to the last character to display. */
464 wout: *t2++ = '\n';
466 /* t2 points one after the last character to display. */
467 if (write(fd, t1, t2 - t1) != t2 - t1)
468 goto werr;
471 if (issync) {
472 rcv_email(sp, mpath);
473 if (close(fd)) {
474 werr: msgq(sp, M_SYSERR, "065|Recovery file");
475 goto err;
478 return (0);
480 err: if (!issync)
481 ep->rcv_fd = -1;
482 if (fd != -1)
483 (void)close(fd);
484 return (1);
488 * people making love
489 * never exactly the same
490 * just like a snowflake
492 * rcv_list --
493 * List the files that can be recovered by this user.
495 * PUBLIC: int rcv_list __P((SCR *));
498 rcv_list(SCR *sp)
500 struct dirent *dp;
501 struct stat sb;
502 DIR *dirp;
503 FILE *fp;
504 int found;
505 char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
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);
516 /* Read the directory. */
517 for (found = 0; (dp = readdir(dirp)) != NULL;) {
518 if (strncmp(dp->d_name, "recover.", 8))
519 continue;
522 * If it's readable, it's recoverable.
524 * XXX
525 * Should be "r", we don't want to write the file. However,
526 * if we're using fcntl(2), there's no way to lock a file
527 * descriptor that's not open for writing.
529 if ((fp = fopen(dp->d_name, "r+")) == NULL)
530 continue;
532 switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
533 case LOCK_FAILED:
535 * XXX
536 * Assume that a lock can't be acquired, but that we
537 * should permit recovery anyway. If this is wrong,
538 * and someone else is using the file, we're going to
539 * die horribly.
541 break;
542 case LOCK_SUCCESS:
543 break;
544 case LOCK_UNAVAIL:
545 /* If it's locked, it's live. */
546 (void)fclose(fp);
547 continue;
550 /* Check the headers. */
551 if (fgets(file, sizeof(file), fp) == NULL ||
552 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
553 (p = strchr(file, '\n')) == NULL ||
554 fgets(path, sizeof(path), fp) == NULL ||
555 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
556 (t = strchr(path, '\n')) == NULL) {
557 msgq_str(sp, M_ERR, dp->d_name,
558 "066|%s: malformed recovery file");
559 goto next;
561 *p = *t = '\0';
564 * If the file doesn't exist, it's an orphaned recovery file,
565 * toss it.
567 * XXX
568 * This can occur if the backup file was deleted and we crashed
569 * before deleting the email file.
571 errno = 0;
572 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
573 errno == ENOENT) {
574 (void)unlink(dp->d_name);
575 goto next;
578 /* Get the last modification time and display. */
579 (void)fstat(fileno(fp), &sb);
580 (void)printf("%.24s: %s\n",
581 ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
582 found = 1;
584 /* Close, discarding lock. */
585 next: (void)fclose(fp);
587 if (found == 0)
588 (void)printf("vi: no files to recover.\n");
589 (void)closedir(dirp);
590 return (0);
594 * rcv_read --
595 * Start a recovered file as the file to edit.
597 * PUBLIC: int rcv_read __P((SCR *, FREF *));
600 rcv_read(SCR *sp, FREF *frp)
602 struct dirent *dp;
603 struct stat sb;
604 DIR *dirp;
605 EXF *ep;
606 time_t rec_mtime;
607 int fd, found, locked, requested, sv_fd;
608 char *name, *p, *t, *rp, *recp, *pathp;
609 char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
611 if (opts_empty(sp, O_RECDIR, 0))
612 return (1);
613 rp = O_STR(sp, O_RECDIR);
614 if ((dirp = opendir(rp)) == NULL) {
615 msgq_str(sp, M_ERR, rp, "%s");
616 return (1);
619 name = frp->name;
620 sv_fd = -1;
621 rec_mtime = 0;
622 recp = pathp = NULL;
623 for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
624 if (strncmp(dp->d_name, "recover.", 8))
625 continue;
626 (void)snprintf(recpath,
627 sizeof(recpath), "%s/%s", rp, dp->d_name);
630 * If it's readable, it's recoverable. It would be very
631 * nice to use stdio(3), but, we can't because that would
632 * require closing and then reopening the file so that we
633 * could have a lock and still close the FP. Another tip
634 * of the hat to fcntl(2).
636 * XXX
637 * Should be O_RDONLY, we don't want to write it. However,
638 * if we're using fcntl(2), there's no way to lock a file
639 * descriptor that's not open for writing.
641 if ((fd = open(recpath, O_RDWR, 0)) == -1)
642 continue;
644 switch (file_lock(sp, NULL, NULL, fd, 1)) {
645 case LOCK_FAILED:
647 * XXX
648 * Assume that a lock can't be acquired, but that we
649 * should permit recovery anyway. If this is wrong,
650 * and someone else is using the file, we're going to
651 * die horribly.
653 locked = 0;
654 break;
655 case LOCK_SUCCESS:
656 locked = 1;
657 break;
658 case LOCK_UNAVAIL:
659 /* If it's locked, it's live. */
660 (void)close(fd);
661 continue;
664 /* Check the headers. */
665 if (rcv_gets(file, sizeof(file), fd) == NULL ||
666 strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
667 (p = strchr(file, '\n')) == NULL ||
668 rcv_gets(path, sizeof(path), fd) == NULL ||
669 strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
670 (t = strchr(path, '\n')) == NULL) {
671 msgq_str(sp, M_ERR, recpath,
672 "067|%s: malformed recovery file");
673 goto next;
675 *p = *t = '\0';
676 ++found;
679 * If the file doesn't exist, it's an orphaned recovery file,
680 * toss it.
682 * XXX
683 * This can occur if the backup file was deleted and we crashed
684 * before deleting the email file.
686 errno = 0;
687 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
688 errno == ENOENT) {
689 (void)unlink(dp->d_name);
690 goto next;
693 /* Check the file name. */
694 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
695 goto next;
697 ++requested;
700 * If we've found more than one, take the most recent.
702 * XXX
703 * Since we're using st_mtime, for portability reasons,
704 * we only get a single second granularity, instead of
705 * getting it right.
707 (void)fstat(fd, &sb);
708 if (recp == NULL || rec_mtime < sb.st_mtime) {
709 p = recp;
710 t = pathp;
711 if ((recp = strdup(recpath)) == NULL) {
712 msgq(sp, M_SYSERR, NULL);
713 recp = p;
714 goto next;
716 if ((pathp = strdup(path)) == NULL) {
717 msgq(sp, M_SYSERR, NULL);
718 free(recp);
719 recp = p;
720 pathp = t;
721 goto next;
723 if (p != NULL) {
724 free(p);
725 free(t);
727 rec_mtime = sb.st_mtime;
728 if (sv_fd != -1)
729 (void)close(sv_fd);
730 sv_fd = fd;
731 } else
732 next: (void)close(fd);
734 (void)closedir(dirp);
736 if (recp == NULL) {
737 msgq_str(sp, M_INFO, name,
738 "068|No files named %s, readable by you, to recover");
739 return (1);
741 if (found) {
742 if (requested > 1)
743 msgq(sp, M_INFO,
744 "069|There are older versions of this file for you to recover");
745 if (found > requested)
746 msgq(sp, M_INFO,
747 "070|There are other files for you to recover");
751 * Create the FREF structure, start the btree file.
753 * XXX
754 * file_init() is going to set ep->rcv_path.
756 if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
757 free(recp);
758 free(pathp);
759 (void)close(sv_fd);
760 return (1);
764 * We keep an open lock on the file so that the recover option can
765 * distinguish between files that are live and those that need to
766 * be recovered. The lock is already acquired, just copy it.
768 ep = sp->ep;
769 ep->rcv_mpath = recp;
770 ep->rcv_fd = sv_fd;
771 if (!locked)
772 F_SET(frp, FR_UNLOCKED);
774 /* We believe the file is recoverable. */
775 F_SET(ep, F_RCV_ON);
776 return (0);
780 * rcv_copy --
781 * Copy a recovery file.
783 static int
784 rcv_copy(SCR *sp, int wfd, char *fname)
786 int nr, nw, off, rfd;
787 char buf[8 * 1024];
789 if ((rfd = open(fname, O_RDONLY, 0)) == -1)
790 goto err;
791 while ((nr = read(rfd, buf, sizeof(buf))) > 0)
792 for (off = 0; nr; nr -= nw, off += nw)
793 if ((nw = write(wfd, buf + off, nr)) < 0)
794 goto err;
795 if (nr == 0)
796 return (0);
798 err: msgq_str(sp, M_SYSERR, fname, "%s");
799 return (1);
803 * rcv_gets --
804 * Fgets(3) for a file descriptor.
806 static char *
807 rcv_gets(char *buf, size_t len, int fd)
809 int nr;
810 char *p;
812 if ((nr = read(fd, buf, len - 1)) == -1)
813 return (NULL);
814 if ((p = strchr(buf, '\n')) == NULL)
815 return (NULL);
816 (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
817 return (buf);
821 * rcv_mktemp --
822 * Paranoid make temporary file routine.
824 static int
825 rcv_mktemp(SCR *sp, char *path, char *dname, int perms)
827 int fd;
830 * !!!
831 * We expect mkstemp(3) to set the permissions correctly. On
832 * historic System V systems, mkstemp didn't. Do it here, on
833 * GP's.
835 * XXX
836 * The variable perms should really be a mode_t, and it would
837 * be nice to use fchmod(2) instead of chmod(2), here.
839 if ((fd = mkstemp(path)) == -1)
840 msgq_str(sp, M_SYSERR, dname, "%s");
841 else
842 (void)chmod(path, perms);
843 return (fd);
847 * rcv_email --
848 * Send email.
850 static void
851 rcv_email(SCR *sp, char *fname)
853 struct stat sb;
854 char buf[MAXPATHLEN * 2 + 20];
856 if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
857 msgq_str(sp, M_SYSERR,
858 _PATH_SENDMAIL, "071|not sending email: %s");
859 else {
861 * !!!
862 * If you need to port this to a system that doesn't have
863 * sendmail, the -t flag causes sendmail to read the message
864 * for the recipients instead of specifying them some other
865 * way.
867 (void)snprintf(buf, sizeof(buf),
868 "%s -t < %s", _PATH_SENDMAIL, fname);
869 (void)system(buf);