home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Spezial / SPEZIAL2_97.zip / SPEZIAL2_97.iso / ANWEND / EDITOR / NVI179B / NVI179B.ZIP / common / recover.c < prev    next >
C/C++ Source or Header  |  1997-06-24  |  25KB  |  936 lines

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