- Add ral(4) for Ralink RT2500/RT2501/RT2600 chip based wireless NIC
[dragonfly.git] / contrib / nvi / common / recover.c
1 /*-
2  * Copyright (c) 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1993, 1994, 1995, 1996
5  *      Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9
10 #include "config.h"
11
12 #ifndef lint
13 static const char sccsid[] = "@(#)recover.c     10.21 (Berkeley) 9/15/96";
14 #endif /* not lint */
15
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>
20
21 /*
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.
25  */
26 #include <sys/file.h>
27
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>
39
40 #include "common.h"
41 #include "pathnames.h"
42
43 /*
44  * Recovery code.
45  *
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:
51  *
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.
58  *
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.)
66  *
67  * To find out if a recovery file/backing file pair are in use, try to get
68  * a lock on the recovery file.
69  *
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.
76  *
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.
80  *
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.
86  *
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.
95  *
96  * The recovery mail file contains normal mail headers, with two additions,
97  * which occur in THIS order, as the FIRST TWO headers:
98  *
99  *      X-vi-recover-file: file_name
100  *      X-vi-recover-path: recover_path
101  *
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.
105  *
106  * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
107  */
108
109 #define VI_FHEADER      "X-vi-recover-file: "
110 #define VI_PHEADER      "X-vi-recover-path: "
111
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));
117
118 /*
119  * rcv_tmp --
120  *      Build a file name that will be used as the recovery file.
121  *
122  * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
123  */
124 int
125 rcv_tmp(sp, ep, name)
126         SCR *sp;
127         EXF *ep;
128         char *name;
129 {
130         struct stat sb;
131         int fd;
132         char *dp, *p, path[MAXPATHLEN];
133
134         /*
135          * !!!
136          * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
137          *
138          *
139          * If the recovery directory doesn't exist, try and create it.  As
140          * the recovery files are themselves protected from reading/writing
141          * by other than the owner, the worst that can happen is that a user
142          * would have permission to remove other user's recovery files.  If
143          * the sticky bit has the BSD semantics, that too will be impossible.
144          */
145         if (opts_empty(sp, O_RECDIR, 0))
146                 goto err;
147         dp = O_STR(sp, O_RECDIR);
148         if (stat(dp, &sb)) {
149                 if (errno != ENOENT || mkdir(dp, 0)) {
150                         msgq(sp, M_SYSERR, "%s", dp);
151                         goto err;
152                 }
153                 (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
154         }
155
156         /* Newlines delimit the mail messages. */
157         for (p = name; *p; ++p)
158                 if (*p == '\n') {
159                         msgq(sp, M_ERR,
160                     "055|Files with newlines in the name are unrecoverable");
161                         goto err;
162                 }
163
164         (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
165         if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
166                 goto err;
167         (void)close(fd);
168
169         if ((ep->rcv_path = strdup(path)) == NULL) {
170                 msgq(sp, M_SYSERR, NULL);
171                 (void)unlink(path);
172 err:            msgq(sp, M_ERR,
173                     "056|Modifications not recoverable if the session fails");
174                 return (1);
175         }
176
177         /* We believe the file is recoverable. */
178         F_SET(ep, F_RCV_ON);
179         return (0);
180 }
181
182 /*
183  * rcv_init --
184  *      Force the file to be snapshotted for recovery.
185  *
186  * PUBLIC: int rcv_init __P((SCR *));
187  */
188 int
189 rcv_init(sp)
190         SCR *sp;
191 {
192         EXF *ep;
193         recno_t lno;
194
195         ep = sp->ep;
196
197         /* Only do this once. */
198         F_CLR(ep, F_FIRSTMODIFY);
199
200         /* If we already know the file isn't recoverable, we're done. */
201         if (!F_ISSET(ep, F_RCV_ON))
202                 return (0);
203
204         /* Turn off recoverability until we figure out if this will work. */
205         F_CLR(ep, F_RCV_ON);
206
207         /* Test if we're recovering a file, not editing one. */
208         if (ep->rcv_mpath == NULL) {
209                 /* Build a file to mail to the user. */
210                 if (rcv_mailfile(sp, 0, NULL))
211                         goto err;
212
213                 /* Force a read of the entire file. */
214                 if (db_last(sp, &lno))
215                         goto err;
216
217                 /* Turn on a busy message, and sync it to backing store. */
218                 sp->gp->scr_busy(sp,
219                     "057|Copying file for recovery...", BUSY_ON);
220                 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
221                         msgq_str(sp, M_SYSERR, ep->rcv_path,
222                             "058|Preservation failed: %s");
223                         sp->gp->scr_busy(sp, NULL, BUSY_OFF);
224                         goto err;
225                 }
226                 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
227         }
228
229         /* Turn off the owner execute bit. */
230         (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
231
232         /* We believe the file is recoverable. */
233         F_SET(ep, F_RCV_ON);
234         return (0);
235
236 err:    msgq(sp, M_ERR,
237             "059|Modifications not recoverable if the session fails");
238         return (1);
239 }
240
241 /*
242  * rcv_sync --
243  *      Sync the file, optionally:
244  *              flagging the backup file to be preserved
245  *              snapshotting the backup file and send email to the user
246  *              sending email to the user if the file was modified
247  *              ending the file session
248  *
249  * PUBLIC: int rcv_sync __P((SCR *, u_int));
250  */
251 int
252 rcv_sync(sp, flags)
253         SCR *sp;
254         u_int flags;
255 {
256         EXF *ep;
257         int fd, rval;
258         char *dp, buf[1024];
259
260         /* Make sure that there's something to recover/sync. */
261         ep = sp->ep;
262         if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
263                 return (0);
264
265         /* Sync the file if it's been modified. */
266         if (F_ISSET(ep, F_MODIFIED)) {
267                 SIGBLOCK;
268                 if (ep->db->sync(ep->db, R_RECNOSYNC)) {
269                         F_CLR(ep, F_RCV_ON | F_RCV_NORM);
270                         msgq_str(sp, M_SYSERR,
271                             ep->rcv_path, "060|File backup failed: %s");
272                         SIGUNBLOCK;
273                         return (1);
274                 }
275                 SIGUNBLOCK;
276
277                 /* REQUEST: don't remove backing file on exit. */
278                 if (LF_ISSET(RCV_PRESERVE))
279                         F_SET(ep, F_RCV_NORM);
280
281                 /* REQUEST: send email. */
282                 if (LF_ISSET(RCV_EMAIL))
283                         rcv_email(sp, ep->rcv_mpath);
284         }
285
286         /*
287          * !!!
288          * Each time the user exec's :preserve, we have to snapshot all of
289          * the recovery information, i.e. it's like the user re-edited the
290          * file.  We copy the DB(3) backing file, and then create a new mail
291          * recovery file, it's simpler than exiting and reopening all of the
292          * underlying files.
293          *
294          * REQUEST: snapshot the file.
295          */
296         rval = 0;
297         if (LF_ISSET(RCV_SNAPSHOT)) {
298                 if (opts_empty(sp, O_RECDIR, 0))
299                         goto err;
300                 dp = O_STR(sp, O_RECDIR);
301                 (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
302                 if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
303                         goto err;
304                 sp->gp->scr_busy(sp,
305                     "061|Copying file for recovery...", BUSY_ON);
306                 if (rcv_copy(sp, fd, ep->rcv_path) ||
307                     close(fd) || rcv_mailfile(sp, 1, buf)) {
308                         (void)unlink(buf);
309                         (void)close(fd);
310                         rval = 1;
311                 }
312                 sp->gp->scr_busy(sp, NULL, BUSY_OFF);
313         }
314         if (0) {
315 err:            rval = 1;
316         }
317
318         /* REQUEST: end the file session. */
319         if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
320                 rval = 1;
321
322         return (rval);
323 }
324
325 /*
326  * rcv_mailfile --
327  *      Build the file to mail to the user.
328  */
329 static int
330 rcv_mailfile(sp, issync, cp_path)
331         SCR *sp;
332         int issync;
333         char *cp_path;
334 {
335         EXF *ep;
336         GS *gp;
337         struct passwd *pw;
338         size_t len;
339         time_t now;
340         uid_t uid;
341         int fd;
342         char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
343         char *t1, *t2, *t3;
344
345         /*
346          * XXX
347          * MAXHOSTNAMELEN is in various places on various systems, including
348          * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
349          */
350 #ifndef MAXHOSTNAMELEN
351 #define MAXHOSTNAMELEN  1024
352 #endif
353         char host[MAXHOSTNAMELEN];
354
355         gp = sp->gp;
356         if ((pw = getpwuid(uid = getuid())) == NULL) {
357                 msgq(sp, M_ERR,
358                     "062|Information on user id %u not found", uid);
359                 return (1);
360         }
361
362         if (opts_empty(sp, O_RECDIR, 0))
363                 return (1);
364         dp = O_STR(sp, O_RECDIR);
365         (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
366         if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
367                 return (1);
368
369         /*
370          * XXX
371          * We keep an open lock on the file so that the recover option can
372          * distinguish between files that are live and those that need to
373          * be recovered.  There's an obvious window between the mkstemp call
374          * and the lock, but it's pretty small.
375          */
376         ep = sp->ep;
377         if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
378                 msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
379         if (!issync) {
380                 /* Save the recover file descriptor, and mail path. */
381                 ep->rcv_fd = fd;
382                 if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
383                         msgq(sp, M_SYSERR, NULL);
384                         goto err;
385                 }
386                 cp_path = ep->rcv_path;
387         }
388
389         /*
390          * XXX
391          * We can't use stdio(3) here.  The problem is that we may be using
392          * fcntl(2), so if ANY file descriptor into the file is closed, the
393          * lock is lost.  So, we could never close the FILE *, even if we
394          * dup'd the fd first.
395          */
396         t = sp->frp->name;
397         if ((p = strrchr(t, '/')) == NULL)
398                 p = t;
399         else
400                 ++p;
401         (void)time(&now);
402         (void)gethostname(host, sizeof(host));
403         len = snprintf(buf, sizeof(buf),
404             "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
405             VI_FHEADER, t,                      /* Non-standard. */
406             VI_PHEADER, cp_path,                /* Non-standard. */
407             "Reply-To: root",
408             "From: root (Nvi recovery program)",
409             "To: ", pw->pw_name,
410             "Subject: Nvi saved the file ", p,
411             "Precedence: bulk");                /* For vacation(1). */
412         if (len > sizeof(buf) - 1)
413                 goto lerr;
414         if (write(fd, buf, len) != len)
415                 goto werr;
416
417         len = snprintf(buf, sizeof(buf),
418             "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
419             "On ", ctime(&now), ", the user ", pw->pw_name,
420             " was editing a file named ", t, " on the machine ",
421             host, ", when it was saved for recovery. ",
422             "You can recover most, if not all, of the changes ",
423             "to this file using the -r option to ", gp->progname, ":\n\n\t",
424             gp->progname, " -r ", t);
425         if (len > sizeof(buf) - 1) {
426 lerr:           msgq(sp, M_ERR, "064|Recovery file buffer overrun");
427                 goto err;
428         }
429
430         /*
431          * Format the message.  (Yes, I know it's silly.)
432          * Requires that the message end in a <newline>.
433          */
434 #define FMTCOLS 60
435         for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
436                 /* Check for a short length. */
437                 if (len <= FMTCOLS) {
438                         t2 = t1 + (len - 1);
439                         goto wout;
440                 }
441
442                 /* Check for a required <newline>. */
443                 t2 = strchr(t1, '\n');
444                 if (t2 - t1 <= FMTCOLS)
445                         goto wout;
446
447                 /* Find the closest space, if any. */
448                 for (t3 = t2; t2 > t1; --t2)
449                         if (*t2 == ' ') {
450                                 if (t2 - t1 <= FMTCOLS)
451                                         goto wout;
452                                 t3 = t2;
453                         }
454                 t2 = t3;
455
456                 /* t2 points to the last character to display. */
457 wout:           *t2++ = '\n';
458
459                 /* t2 points one after the last character to display. */
460                 if (write(fd, t1, t2 - t1) != t2 - t1)
461                         goto werr;
462         }
463
464         if (issync) {
465                 rcv_email(sp, mpath);
466                 if (close(fd)) {
467 werr:                   msgq(sp, M_SYSERR, "065|Recovery file");
468                         goto err;
469                 }
470         }
471         return (0);
472
473 err:    if (!issync)
474                 ep->rcv_fd = -1;
475         if (fd != -1)
476                 (void)close(fd);
477         return (1);
478 }
479
480 /*
481  *      people making love
482  *      never exactly the same
483  *      just like a snowflake
484  *
485  * rcv_list --
486  *      List the files that can be recovered by this user.
487  *
488  * PUBLIC: int rcv_list __P((SCR *));
489  */
490 int
491 rcv_list(sp)
492         SCR *sp;
493 {
494         struct dirent *dp;
495         struct stat sb;
496         DIR *dirp;
497         FILE *fp;
498         int found;
499         char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
500
501         /* Open the recovery directory for reading. */
502         if (opts_empty(sp, O_RECDIR, 0))
503                 return (1);
504         p = O_STR(sp, O_RECDIR);
505         if (chdir(p) || (dirp = opendir(".")) == NULL) {
506                 msgq_str(sp, M_SYSERR, p, "recdir: %s");
507                 return (1);
508         }
509
510         /* Read the directory. */
511         for (found = 0; (dp = readdir(dirp)) != NULL;) {
512                 if (strncmp(dp->d_name, "recover.", 8))
513                         continue;
514
515                 /*
516                  * If it's readable, it's recoverable.
517                  *
518                  * XXX
519                  * Should be "r", we don't want to write the file.  However,
520                  * if we're using fcntl(2), there's no way to lock a file
521                  * descriptor that's not open for writing.
522                  */
523                 if ((fp = fopen(dp->d_name, "r+")) == NULL)
524                         continue;
525
526                 switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
527                 case LOCK_FAILED:
528                         /*
529                          * XXX
530                          * Assume that a lock can't be acquired, but that we
531                          * should permit recovery anyway.  If this is wrong,
532                          * and someone else is using the file, we're going to
533                          * die horribly.
534                          */
535                         break;
536                 case LOCK_SUCCESS:
537                         break;
538                 case LOCK_UNAVAIL:
539                         /* If it's locked, it's live. */
540                         (void)fclose(fp);
541                         continue;
542                 }
543
544                 /* Check the headers. */
545                 if (fgets(file, sizeof(file), fp) == NULL ||
546                     strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
547                     (p = strchr(file, '\n')) == NULL ||
548                     fgets(path, sizeof(path), fp) == NULL ||
549                     strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
550                     (t = strchr(path, '\n')) == NULL) {
551                         msgq_str(sp, M_ERR, dp->d_name,
552                             "066|%s: malformed recovery file");
553                         goto next;
554                 }
555                 *p = *t = '\0';
556
557                 /*
558                  * If the file doesn't exist, it's an orphaned recovery file,
559                  * toss it.
560                  *
561                  * XXX
562                  * This can occur if the backup file was deleted and we crashed
563                  * before deleting the email file.
564                  */
565                 errno = 0;
566                 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
567                     errno == ENOENT) {
568                         (void)unlink(dp->d_name);
569                         goto next;
570                 }
571
572                 /* Get the last modification time and display. */
573                 (void)fstat(fileno(fp), &sb);
574                 (void)printf("%.24s: %s\n",
575                     ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
576                 found = 1;
577
578                 /* Close, discarding lock. */
579 next:           (void)fclose(fp);
580         }
581         if (found == 0)
582                 (void)printf("vi: no files to recover.\n");
583         (void)closedir(dirp);
584         return (0);
585 }
586
587 /*
588  * rcv_read --
589  *      Start a recovered file as the file to edit.
590  *
591  * PUBLIC: int rcv_read __P((SCR *, FREF *));
592  */
593 int
594 rcv_read(sp, frp)
595         SCR *sp;
596         FREF *frp;
597 {
598         struct dirent *dp;
599         struct stat sb;
600         DIR *dirp;
601         EXF *ep;
602         time_t rec_mtime;
603         int fd, found, locked, requested, sv_fd;
604         char *name, *p, *t, *rp, *recp, *pathp;
605         char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
606
607         if (opts_empty(sp, O_RECDIR, 0))
608                 return (1);
609         rp = O_STR(sp, O_RECDIR);
610         if ((dirp = opendir(rp)) == NULL) {
611                 msgq_str(sp, M_ERR, rp, "%s");
612                 return (1);
613         }
614
615         name = frp->name;
616         sv_fd = -1;
617         rec_mtime = 0;
618         recp = pathp = NULL;
619         for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
620                 if (strncmp(dp->d_name, "recover.", 8))
621                         continue;
622                 (void)snprintf(recpath,
623                     sizeof(recpath), "%s/%s", rp, dp->d_name);
624
625                 /*
626                  * If it's readable, it's recoverable.  It would be very
627                  * nice to use stdio(3), but, we can't because that would
628                  * require closing and then reopening the file so that we
629                  * could have a lock and still close the FP.  Another tip
630                  * of the hat to fcntl(2).
631                  *
632                  * XXX
633                  * Should be O_RDONLY, we don't want to write it.  However,
634                  * if we're using fcntl(2), there's no way to lock a file
635                  * descriptor that's not open for writing.
636                  */
637                 if ((fd = open(recpath, O_RDWR, 0)) == -1)
638                         continue;
639
640                 switch (file_lock(sp, NULL, NULL, fd, 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)close(fd);
657                         continue;
658                 }
659
660                 /* Check the headers. */
661                 if (rcv_gets(file, sizeof(file), fd) == NULL ||
662                     strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
663                     (p = strchr(file, '\n')) == NULL ||
664                     rcv_gets(path, sizeof(path), fd) == NULL ||
665                     strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
666                     (t = strchr(path, '\n')) == NULL) {
667                         msgq_str(sp, M_ERR, recpath,
668                             "067|%s: malformed recovery file");
669                         goto next;
670                 }
671                 *p = *t = '\0';
672                 ++found;
673
674                 /*
675                  * If the file doesn't exist, it's an orphaned recovery file,
676                  * toss it.
677                  *
678                  * XXX
679                  * This can occur if the backup file was deleted and we crashed
680                  * before deleting the email file.
681                  */
682                 errno = 0;
683                 if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
684                     errno == ENOENT) {
685                         (void)unlink(dp->d_name);
686                         goto next;
687                 }
688
689                 /* Check the file name. */
690                 if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
691                         goto next;
692
693                 ++requested;
694
695                 /*
696                  * If we've found more than one, take the most recent.
697                  *
698                  * XXX
699                  * Since we're using st_mtime, for portability reasons,
700                  * we only get a single second granularity, instead of
701                  * getting it right.
702                  */
703                 (void)fstat(fd, &sb);
704                 if (recp == NULL || rec_mtime < sb.st_mtime) {
705                         p = recp;
706                         t = pathp;
707                         if ((recp = strdup(recpath)) == NULL) {
708                                 msgq(sp, M_SYSERR, NULL);
709                                 recp = p;
710                                 goto next;
711                         }
712                         if ((pathp = strdup(path)) == NULL) {
713                                 msgq(sp, M_SYSERR, NULL);
714                                 free(recp);
715                                 recp = p;
716                                 pathp = t;
717                                 goto next;
718                         }
719                         if (p != NULL) {
720                                 free(p);
721                                 free(t);
722                         }
723                         rec_mtime = sb.st_mtime;
724                         if (sv_fd != -1)
725                                 (void)close(sv_fd);
726                         sv_fd = fd;
727                 } else
728 next:                   (void)close(fd);
729         }
730         (void)closedir(dirp);
731
732         if (recp == NULL) {
733                 msgq_str(sp, M_INFO, name,
734                     "068|No files named %s, readable by you, to recover");
735                 return (1);
736         }
737         if (found) {
738                 if (requested > 1)
739                         msgq(sp, M_INFO,
740             "069|There are older versions of this file for you to recover");
741                 if (found > requested)
742                         msgq(sp, M_INFO,
743                             "070|There are other files for you to recover");
744         }
745
746         /*
747          * Create the FREF structure, start the btree file.
748          *
749          * XXX
750          * file_init() is going to set ep->rcv_path.
751          */
752         if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
753                 free(recp);
754                 free(pathp);
755                 (void)close(sv_fd);
756                 return (1);
757         }
758
759         /*
760          * We keep an open lock on the file so that the recover option can
761          * distinguish between files that are live and those that need to
762          * be recovered.  The lock is already acquired, just copy it.
763          */
764         ep = sp->ep;
765         ep->rcv_mpath = recp;
766         ep->rcv_fd = sv_fd;
767         if (!locked)
768                 F_SET(frp, FR_UNLOCKED);
769
770         /* We believe the file is recoverable. */
771         F_SET(ep, F_RCV_ON);
772         return (0);
773 }
774
775 /*
776  * rcv_copy --
777  *      Copy a recovery file.
778  */
779 static int
780 rcv_copy(sp, wfd, fname)
781         SCR *sp;
782         int wfd;
783         char *fname;
784 {
785         int nr, nw, off, rfd;
786         char buf[8 * 1024];
787
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);
796
797 err:    msgq_str(sp, M_SYSERR, fname, "%s");
798         return (1);
799 }
800
801 /*
802  * rcv_gets --
803  *      Fgets(3) for a file descriptor.
804  */
805 static char *
806 rcv_gets(buf, len, fd)
807         char *buf;
808         size_t len;
809         int fd;
810 {
811         int nr;
812         char *p;
813
814         if ((nr = read(fd, buf, len - 1)) == -1)
815                 return (NULL);
816         if ((p = strchr(buf, '\n')) == NULL)
817                 return (NULL);
818         (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
819         return (buf);
820 }
821
822 /*
823  * rcv_mktemp --
824  *      Paranoid make temporary file routine.
825  */
826 static int
827 rcv_mktemp(sp, path, dname, perms)
828         SCR *sp;
829         char *path, *dname;
830         int perms;
831 {
832         int fd;
833
834         /*
835          * !!!
836          * We expect mkstemp(3) to set the permissions correctly.  On
837          * historic System V systems, mkstemp didn't.  Do it here, on
838          * GP's.
839          *
840          * XXX
841          * The variable perms should really be a mode_t, and it would
842          * be nice to use fchmod(2) instead of chmod(2), here.
843          */
844         if ((fd = mkstemp(path)) == -1)
845                 msgq_str(sp, M_SYSERR, dname, "%s");
846         else
847                 (void)chmod(path, perms);
848         return (fd);
849 }
850
851 /*
852  * rcv_email --
853  *      Send email.
854  */
855 static void
856 rcv_email(sp, fname)
857         SCR *sp;
858         char *fname;
859 {
860         struct stat sb;
861         char buf[MAXPATHLEN * 2 + 20];
862
863         if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
864                 msgq_str(sp, M_SYSERR,
865                     _PATH_SENDMAIL, "071|not sending email: %s");
866         else {
867                 /*
868                  * !!!
869                  * If you need to port this to a system that doesn't have
870                  * sendmail, the -t flag causes sendmail to read the message
871                  * for the recipients instead of specifying them some other
872                  * way.
873                  */
874                 (void)snprintf(buf, sizeof(buf),
875                     "%s -t < %s", _PATH_SENDMAIL, fname);
876                 (void)system(buf);
877         }
878 }