source: trunk/third/nvi/common/recover.c @ 14302

Revision 14302, 21.8 KB checked in by ghudson, 25 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r14301, which included commits to RCS files with non-trunk default branches.
Line 
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
13static 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
112static int       rcv_copy __P((SCR *, int, char *));
113static void      rcv_email __P((SCR *, char *));
114static char     *rcv_gets __P((char *, size_t, int));
115static int       rcv_mailfile __P((SCR *, int, char *));
116static 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 */
124int
125rcv_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);
172err:            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 */
188int
189rcv_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
236err:    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 */
251int
252rcv_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) {
315err:            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 */
329static int
330rcv_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) {
426lerr:           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. */
457wout:           *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)) {
467werr:                   msgq(sp, M_SYSERR, "065|Recovery file");
468                        goto err;
469                }
470        }
471        return (0);
472
473err:    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 */
490int
491rcv_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. */
579next:           (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 */
593int
594rcv_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
728next:                   (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 */
779static int
780rcv_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
797err:    msgq_str(sp, M_SYSERR, fname, "%s");
798        return (1);
799}
800
801/*
802 * rcv_gets --
803 *      Fgets(3) for a file descriptor.
804 */
805static char *
806rcv_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 */
826static int
827rcv_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 */
855static void
856rcv_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}
Note: See TracBrowser for help on using the repository browser.