1 /*        $NetBSD: recover.c,v 1.11 2017/11/10 20:01:11 christos Exp $ */
2 /*-
3  * Copyright (c) 1993, 1994
4  *        The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1993, 1994, 1995, 1996
6  *        Keith Bostic.  All rights reserved.
7  *
8  * See the LICENSE file for redistribution information.
9  */
10 
11 #include "config.h"
12 
13 #include <sys/cdefs.h>
14 #if 0
15 #ifndef lint
16 static const char sccsid[] = "Id: recover.c,v 10.31 2001/11/01 15:24:44 skimo Exp  (Berkeley) Date: 2001/11/01 15:24:44 ";
17 #endif /* not lint */
18 #else
19 __RCSID("$NetBSD: recover.c,v 1.11 2017/11/10 20:01:11 christos Exp $");
20 #endif
21 
22 #include <sys/param.h>
23 #include <sys/types.h>                  /* XXX: param.h may not have included types.h */
24 #include <sys/queue.h>
25 #include <sys/wait.h>
26 #include <sys/stat.h>
27 
28 /*
29  * We include <sys/file.h>, because the open #defines were found there
30  * on historical systems.  We also include <fcntl.h> because the open(2)
31  * #defines are found there on newer systems.
32  */
33 #include <sys/file.h>
34 
35 #include <bitstring.h>
36 #include <dirent.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <limits.h>
40 #include <pwd.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <time.h>
45 #include <unistd.h>
46 
47 #include "common.h"
48 #include "pathnames.h"
49 
50 /*
51  * Recovery code.
52  *
53  * The basic scheme is as follows.  In the EXF structure, we maintain full
54  * paths of a b+tree file and a mail recovery file.  The former is the file
55  * used as backing store by the DB package.  The latter is the file that
56  * contains an email message to be sent to the user if we crash.  The two
57  * simple states of recovery are:
58  *
59  *        + first starting the edit session:
60  *                  the b+tree file exists and is mode 700, the mail recovery
61  *                  file doesn't exist.
62  *        + after the file has been modified:
63  *                  the b+tree file exists and is mode 600, the mail recovery
64  *                  file exists, and is exclusively locked.
65  *
66  * In the EXF structure we maintain a file descriptor that is the locked
67  * file descriptor for the mail recovery file.  NOTE: we sometimes have to
68  * do locking with fcntl(2).  This is a problem because if you close(2) any
69  * file descriptor associated with the file, ALL of the locks go away.  Be
70  * sure to remember that if you have to modify the recovery code.  (It has
71  * been rhetorically asked of what the designers could have been thinking
72  * when they did that interface.  The answer is simple: they weren't.)
73  *
74  * To find out if a recovery file/backing file pair are in use, try to get
75  * a lock on the recovery file.
76  *
77  * To find out if a backing file can be deleted at boot time, check for an
78  * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
79  * special stuff into the backing file itself, or correlate the files at
80  * boot time, neither of which looks like fun.)  Note also that there's a
81  * window between when the file is created and the X bit is set.  It's small,
82  * but it's there.  To fix the window, check for 0 length files as well.
83  *
84  * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
85  * this DOES NOT mean that any initialization has been done, only that we
86  * haven't yet failed at setting up or doing recovery.
87  *
88  * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
89  * If that bit is not set when ending a file session:
90  *        If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
91  *        they are unlink(2)'d, and free(3)'d.
92  *        If the EXF file descriptor (rcv_fd) is not -1, it is closed.
93  *
94  * The backing b+tree file is set up when a file is first edited, so that
95  * the DB package can use it for on-disk caching and/or to snapshot the
96  * file.  When the file is first modified, the mail recovery file is created,
97  * the backing file permissions are updated, the file is sync(2)'d to disk,
98  * and the timer is started.  Then, at RCV_PERIOD second intervals, the
99  * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
100  * means that the data structures (SCR, EXF, the underlying tree structures)
101  * must be consistent when the signal arrives.
102  *
103  * The recovery mail file contains normal mail headers, with two additions,
104  * which occur in THIS order, as the FIRST TWO headers:
105  *
106  *        X-vi-recover-file: file_name
107  *        X-vi-recover-path: recover_path
108  *
109  * Since newlines delimit the headers, this means that file names cannot have
110  * newlines in them, but that's probably okay.  As these files aren't intended
111  * to be long-lived, changing their format won't be too painful.
112  *
113  * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
114  */
115 
116 #define   VI_FHEADER          "X-vi-recover-file: "
117 #define   VI_PHEADER          "X-vi-recover-path: "
118 
119 static int           rcv_copy(SCR *, int, char *);
120 static void          rcv_email(SCR *, int fd);
121 static char         *rcv_gets(char *, size_t, int);
122 static int           rcv_mailfile(SCR *, int, char *);
123 static int           rcv_mktemp(SCR *, char *, const char *, int);
124 
125 #ifndef O_REGULAR
126 #define O_REGULAR O_NONBLOCK
127 #endif
128 
129 /*
130  * rcv_tmp --
131  *        Build a file name that will be used as the recovery file.
132  *
133  * PUBLIC: int rcv_tmp(SCR *, EXF *, char *);
134  */
135 int
rcv_tmp(SCR * sp,EXF * ep,char * name)136 rcv_tmp(SCR *sp, EXF *ep, char *name)
137 {
138           struct stat sb;
139           int fd;
140           char path[MAXPATHLEN];
141           const char *dp;
142 
143           /*
144            * !!!
145            * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
146            *
147            *
148            * If the recovery directory doesn't exist, try and create it.  As
149            * the recovery files are themselves protected from reading/writing
150            * by other than the owner, the worst that can happen is that a user
151            * would have permission to remove other user's recovery files.  If
152            * the sticky bit has the BSD semantics, that too will be impossible.
153            */
154           if (opts_empty(sp, O_RECDIR, 0))
155                     goto err;
156           dp = O_STR(sp, O_RECDIR);
157           if (stat(dp, &sb)) {
158                     if (errno != ENOENT || mkdir(dp, 0)) {
159                               msgq(sp, M_SYSERR, "%s", dp);
160                               goto err;
161                     }
162                     (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
163           }
164 
165           /* Newlines delimit the mail messages. */
166           if (strchr(name, '\n')) {
167                     msgq(sp, M_ERR,
168                         "055|Files with newlines in the name are unrecoverable");
169                     goto err;
170           }
171 
172           (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
173           if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
174                     goto err;
175           (void)close(fd);
176 
177           if ((ep->rcv_path = strdup(path)) == NULL) {
178                     msgq(sp, M_SYSERR, NULL);
179                     (void)unlink(path);
180 err:                msgq(sp, M_ERR,
181                         "056|Modifications not recoverable if the session fails");
182                     return (1);
183           }
184 
185           /* We believe the file is recoverable. */
186           F_SET(ep, F_RCV_ON);
187           return (0);
188 }
189 
190 /*
191  * rcv_init --
192  *        Force the file to be snapshotted for recovery.
193  *
194  * PUBLIC: int rcv_init(SCR *);
195  */
196 int
rcv_init(SCR * sp)197 rcv_init(SCR *sp)
198 {
199           EXF *ep;
200           db_recno_t lno;
201 
202           ep = sp->ep;
203 
204           /* Only do this once. */
205           F_CLR(ep, F_FIRSTMODIFY);
206 
207           /* If we already know the file isn't recoverable, we're done. */
208           if (!F_ISSET(ep, F_RCV_ON))
209                     return (0);
210 
211           /* Turn off recoverability until we figure out if this will work. */
212           F_CLR(ep, F_RCV_ON);
213 
214           /* Test if we're recovering a file, not editing one. */
215           if (ep->rcv_mpath == NULL) {
216                     /* Build a file to mail to the user. */
217                     if (rcv_mailfile(sp, 0, NULL))
218                               goto err;
219 
220                     /* Force a read of the entire file. */
221                     if (db_last(sp, &lno))
222                               goto err;
223 
224                     /* Turn on a busy message, and sync it to backing store. */
225                     sp->gp->scr_busy(sp,
226                         "057|Copying file for recovery...", BUSY_ON);
227                     if (ep->db->sync(ep->db, 0)) {
228                               msgq_str(sp, M_SYSERR, ep->rcv_path,
229                                   "058|Preservation failed: %s");
230                               sp->gp->scr_busy(sp, NULL, BUSY_OFF);
231                               goto err;
232                     }
233                     sp->gp->scr_busy(sp, NULL, BUSY_OFF);
234           }
235 
236           /* Turn off the owner execute bit. */
237           (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
238 
239           /* We believe the file is recoverable. */
240           F_SET(ep, F_RCV_ON);
241           return (0);
242 
243 err:      msgq(sp, M_ERR,
244               "059|Modifications not recoverable if the session fails");
245           return (1);
246 }
247 
248 /*
249  * rcv_sync --
250  *        Sync the file, optionally:
251  *                  flagging the backup file to be preserved
252  *                  snapshotting the backup file and send email to the user
253  *                  sending email to the user if the file was modified
254  *                  ending the file session
255  *
256  * PUBLIC: int rcv_sync(SCR *, u_int);
257  */
258 int
rcv_sync(SCR * sp,u_int flags)259 rcv_sync(SCR *sp, u_int flags)
260 {
261           EXF *ep;
262           int fd, rval;
263           char buf[1024];
264           const char *dp;
265 
266           /* Make sure that there's something to recover/sync. */
267           ep = sp->ep;
268           if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
269                     return (0);
270 
271           /* Sync the file if it's been modified. */
272           if (F_ISSET(ep, F_MODIFIED)) {
273                     /*
274                      * If we are using a db1 version of the database,
275                      * we want to sync the underlying btree not the
276                      * recno tree which is transient anyway.
277                      */
278 #ifndef R_RECNOSYNC
279 #define   R_RECNOSYNC 0
280 #endif
281                     if (ep->db->sync(ep->db, R_RECNOSYNC)) {
282                               F_CLR(ep, F_RCV_ON | F_RCV_NORM);
283                               msgq_str(sp, M_SYSERR,
284                                   ep->rcv_path, "060|File backup failed: %s");
285                               return (1);
286                     }
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_fd);
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 ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
314                               goto err;
315                     sp->gp->scr_busy(sp,
316                         "061|Copying file for recovery...", BUSY_ON);
317                     if (rcv_copy(sp, fd, ep->rcv_path) ||
318                         close(fd) || rcv_mailfile(sp, 1, buf)) {
319                               (void)unlink(buf);
320                               (void)close(fd);
321                               rval = 1;
322                     }
323                     sp->gp->scr_busy(sp, NULL, BUSY_OFF);
324           }
325           if (0) {
326 err:                rval = 1;
327           }
328 
329           /* REQUEST: end the file session. */
330           if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
331                     rval = 1;
332 
333           return (rval);
334 }
335 
336 /*
337  * rcv_mailfile --
338  *        Build the file to mail to the user.
339  */
340 static int
rcv_mailfile(SCR * sp,int issync,char * cp_path)341 rcv_mailfile(SCR *sp, int issync, char *cp_path)
342 {
343           EXF *ep;
344           GS *gp;
345           struct passwd *pw;
346           size_t len;
347           time_t now;
348           uid_t uid;
349           int fd;
350           char *p, *t, buf[4096], mpath[MAXPATHLEN];
351           const char *dp;
352           char *t1, *t2, *t3;
353 
354           /*
355            * XXX
356            * MAXHOSTNAMELEN is in various places on various systems, including
357            * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
358            */
359 #ifndef MAXHOSTNAMELEN
360 #define   MAXHOSTNAMELEN      1024
361 #endif
362           char host[MAXHOSTNAMELEN];
363 
364           gp = sp->gp;
365           if ((pw = getpwuid(uid = getuid())) == NULL) {
366                     msgq(sp, M_ERR,
367                         "062|Information on user id %u not found", uid);
368                     return (1);
369           }
370 
371           if (opts_empty(sp, O_RECDIR, 0))
372                     return (1);
373           dp = O_STR(sp, O_RECDIR);
374           (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
375           if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
376                     return (1);
377 
378           /*
379            * XXX
380            * We keep an open lock on the file so that the recover option can
381            * distinguish between files that are live and those that need to
382            * be recovered.  There's an obvious window between the mkstemp call
383            * and the lock, but it's pretty small.
384            */
385           ep = sp->ep;
386           if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
387                     msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
388           if (!issync) {
389                     /* Save the recover file descriptor, and mail path. */
390                     ep->rcv_fd = fd;
391                     if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
392                               msgq(sp, M_SYSERR, NULL);
393                               goto err;
394                     }
395                     cp_path = ep->rcv_path;
396           }
397 
398           /*
399            * XXX
400            * We can't use stdio(3) here.  The problem is that we may be using
401            * fcntl(2), so if ANY file descriptor into the file is closed, the
402            * lock is lost.  So, we could never close the FILE *, even if we
403            * dup'd the fd first.
404            */
405           t = sp->frp->name;
406           if ((p = strrchr(t, '/')) == NULL)
407                     p = t;
408           else
409                     ++p;
410           (void)time(&now);
411           (void)gethostname(host, sizeof(host));
412           len = snprintf(buf, sizeof(buf),
413               "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
414               VI_FHEADER, t,                      /* Non-standard. */
415               VI_PHEADER, cp_path,                /* Non-standard. */
416               "Reply-To: root",
417               "From: root (Nvi recovery program)",
418               "To: ", pw->pw_name,
419               "Subject: Nvi saved the file ", p,
420               "Precedence: bulk");                /* For vacation(1). */
421           if (len > sizeof(buf) - 1)
422                     goto lerr;
423           if ((size_t)write(fd, buf, len) != len)
424                     goto werr;
425 
426           len = snprintf(buf, sizeof(buf),
427               "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
428               "On ", ctime(&now), ", the user ", pw->pw_name,
429               " was editing a file named ", t, " on the machine ",
430               host, ", when it was saved for recovery. ",
431               "You can recover most, if not all, of the changes ",
432               "to this file using the -r option to ", gp->progname, ":\n\n\t",
433               gp->progname, " -r ", t);
434           if (len > sizeof(buf) - 1) {
435 lerr:               msgq(sp, M_ERR, "064|Recovery file buffer overrun");
436                     goto err;
437           }
438 
439           /*
440            * Format the message.  (Yes, I know it's silly.)
441            * Requires that the message end in a <newline>.
442            */
443 #define   FMTCOLS   60
444           for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
445                     /* Check for a short length. */
446                     if (len <= FMTCOLS) {
447                               t2 = t1 + (len - 1);
448                               goto wout;
449                     }
450 
451                     /* Check for a required <newline>. */
452                     t2 = strchr(t1, '\n');
453                     if (t2 - t1 <= FMTCOLS)
454                               goto wout;
455 
456                     /* Find the closest space, if any. */
457                     for (t3 = t2; t2 > t1; --t2)
458                               if (*t2 == ' ') {
459                                         if (t2 - t1 <= FMTCOLS)
460                                                   goto wout;
461                                         t3 = t2;
462                               }
463                     t2 = t3;
464 
465                     /* t2 points to the last character to display. */
466 wout:               *t2++ = '\n';
467 
468                     /* t2 points one after the last character to display. */
469                     if (write(fd, t1, t2 - t1) != t2 - t1)
470                               goto werr;
471           }
472 
473           if (issync) {
474                     rcv_email(sp, fd);
475                     if (close(fd)) {
476 werr:                         msgq(sp, M_SYSERR, "065|Recovery file");
477                               goto err;
478                     }
479           }
480           return (0);
481 
482 err:      if (!issync)
483                     ep->rcv_fd = -1;
484           if (fd != -1)
485                     (void)close(fd);
486           return (1);
487 }
488 
489 /*
490  * Since vi creates recovery files only accessible by the user, files
491  * accessible by group or others are probably malicious so avoid them.
492  * This is simpler than checking for getuid() == st.st_uid and we want
493  * to preserve the functionality that root can recover anything which
494  * means that root should know better and be careful.
495  *
496  * Checking the mode is racy though (someone can chmod between the
497  * open and the stat call, so also check for uid match or root.
498  */
499 static int
checkok(int fd)500 checkok(int fd)
501 {
502           struct stat sb;
503           uid_t uid = getuid();
504 
505           return fstat(fd, &sb) != -1 && S_ISREG(sb.st_mode) &&
506               (sb.st_mode & (S_IRWXG|S_IRWXO)) == 0 &&
507               (uid == 0 || uid == sb.st_uid);
508 }
509 
510 /*
511  *        people making love
512  *        never exactly the same
513  *        just like a snowflake
514  *
515  * rcv_list --
516  *        List the files that can be recovered by this user.
517  *
518  * PUBLIC: int rcv_list(SCR *);
519  */
520 int
rcv_list(SCR * sp)521 rcv_list(SCR *sp)
522 {
523           struct dirent *dp;
524           struct stat sb;
525           DIR *dirp;
526           FILE *fp;
527           int found;
528           char *p, *t;
529           const char *d;
530           char file[MAXPATHLEN], path[MAXPATHLEN];
531 
532           /* Open the recovery directory for reading. */
533           if (opts_empty(sp, O_RECDIR, 0))
534                     return (1);
535           d = O_STR(sp, O_RECDIR);
536           if (chdir(d) || (dirp = opendir(".")) == NULL) {
537                     msgq_str(sp, M_SYSERR, d, "recdir: %s");
538                     return (1);
539           }
540 
541           /* Read the directory. */
542           for (found = 0; (dp = readdir(dirp)) != NULL;) {
543                     if (strncmp(dp->d_name, "recover.", 8))
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+efl")) == NULL)
555                               continue;
556 
557                     if (!checkok(fileno(fp))) {
558                               (void)fclose(fp);
559                               continue;
560                     }
561 
562                     switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
563                     case LOCK_FAILED:
564                               /*
565                                * XXX
566                                * Assume that a lock can't be acquired, but that we
567                                * should permit recovery anyway.  If this is wrong,
568                                * and someone else is using the file, we're going to
569                                * die horribly.
570                                */
571                               break;
572                     case LOCK_SUCCESS:
573                               break;
574                     case LOCK_UNAVAIL:
575                               /* If it's locked, it's live. */
576                               (void)fclose(fp);
577                               continue;
578                     }
579 
580                     /* Check the headers. */
581                     if (fgets(file, sizeof(file), fp) == NULL ||
582                         strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
583                         (p = strchr(file, '\n')) == NULL ||
584                         fgets(path, sizeof(path), fp) == NULL ||
585                         strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
586                         (t = strchr(path, '\n')) == NULL) {
587                               msgq_str(sp, M_ERR, dp->d_name,
588                                   "066|%s: malformed recovery file");
589                               goto next;
590                     }
591                     *p = *t = '\0';
592 
593                     /*
594                      * If the file doesn't exist, it's an orphaned recovery file,
595                      * toss it.
596                      *
597                      * XXX
598                      * This can occur if the backup file was deleted and we crashed
599                      * before deleting the email file.
600                      */
601                     errno = 0;
602                     if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
603                         errno == ENOENT) {
604                               (void)unlink(dp->d_name);
605                               goto next;
606                     }
607 
608                     /* Get the last modification time and display. */
609                     (void)fstat(fileno(fp), &sb);
610                     (void)printf("%.24s: %s\n",
611                         ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
612                     found = 1;
613 
614                     /* Close, discarding lock. */
615 next:               (void)fclose(fp);
616           }
617           if (found == 0)
618                     (void)printf("%s: No files to recover\n", sp->gp->progname);
619           (void)closedir(dirp);
620           return (0);
621 }
622 
623 /*
624  * rcv_read --
625  *        Start a recovered file as the file to edit.
626  *
627  * PUBLIC: int rcv_read(SCR *, FREF *);
628  */
629 int
rcv_read(SCR * sp,FREF * frp)630 rcv_read(SCR *sp, FREF *frp)
631 {
632           struct dirent *dp;
633           struct stat sb;
634           DIR *dirp;
635           EXF *ep;
636           time_t rec_mtime;
637           int fd, found, locked = 0, requested, sv_fd;
638           char *name, *p, *t, *recp, *pathp;
639           const char *rp;
640           char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
641 
642           if (opts_empty(sp, O_RECDIR, 0))
643                     return (1);
644           rp = O_STR(sp, O_RECDIR);
645           if ((dirp = opendir(rp)) == NULL) {
646                     msgq_str(sp, M_ERR, rp, "%s");
647                     return (1);
648           }
649 
650           name = frp->name;
651           sv_fd = -1;
652           rec_mtime = 0;
653           recp = pathp = NULL;
654           for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
655                     if (strncmp(dp->d_name, "recover.", 8))
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|O_REGULAR|O_NOFOLLOW|O_CLOEXEC,
673                         0)) == -1)
674                               continue;
675 
676                     if (!checkok(fd)) {
677                               (void)close(fd);
678                               continue;
679                     }
680 
681                     switch (file_lock(sp, NULL, NULL, fd, 1)) {
682                     case LOCK_FAILED:
683                               /*
684                                * XXX
685                                * Assume that a lock can't be acquired, but that we
686                                * should permit recovery anyway.  If this is wrong,
687                                * and someone else is using the file, we're going to
688                                * die horribly.
689                                */
690                               locked = 0;
691                               break;
692                     case LOCK_SUCCESS:
693                               locked = 1;
694                               break;
695                     case LOCK_UNAVAIL:
696                               /* If it's locked, it's live. */
697                               (void)close(fd);
698                               continue;
699                     }
700 
701                     /* Check the headers. */
702                     if (rcv_gets(file, sizeof(file), fd) == NULL ||
703                         strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
704                         (p = strchr(file, '\n')) == NULL ||
705                         rcv_gets(path, sizeof(path), fd) == NULL ||
706                         strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
707                         (t = strchr(path, '\n')) == NULL) {
708                               msgq_str(sp, M_ERR, recpath,
709                                   "067|%s: malformed recovery file");
710                               goto next;
711                     }
712                     *p = *t = '\0';
713                     ++found;
714 
715                     /*
716                      * If the file doesn't exist, it's an orphaned recovery file,
717                      * toss it.
718                      *
719                      * XXX
720                      * This can occur if the backup file was deleted and we crashed
721                      * before deleting the email file.
722                      */
723                     errno = 0;
724                     if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
725                         errno == ENOENT) {
726                               (void)unlink(dp->d_name);
727                               goto next;
728                     }
729 
730                     /* Check the file name. */
731                     if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
732                               goto next;
733 
734                     ++requested;
735 
736                     /*
737                      * If we've found more than one, take the most recent.
738                      *
739                      * XXX
740                      * Since we're using st_mtime, for portability reasons,
741                      * we only get a single second granularity, instead of
742                      * getting it right.
743                      */
744                     (void)fstat(fd, &sb);
745                     if (recp == NULL || rec_mtime < sb.st_mtime) {
746                               p = recp;
747                               t = pathp;
748                               if ((recp = strdup(recpath)) == NULL) {
749                                         msgq(sp, M_SYSERR, NULL);
750                                         recp = p;
751                                         goto next;
752                               }
753                               if ((pathp = strdup(path)) == NULL) {
754                                         msgq(sp, M_SYSERR, NULL);
755                                         free(recp);
756                                         recp = p;
757                                         pathp = t;
758                                         goto next;
759                               }
760                               if (p != NULL) {
761                                         free(p);
762                                         free(t);
763                               }
764                               rec_mtime = sb.st_mtime;
765                               if (sv_fd != -1)
766                                         (void)close(sv_fd);
767                               sv_fd = fd;
768                     } else
769 next:                         (void)close(fd);
770           }
771           (void)closedir(dirp);
772 
773           if (recp == NULL) {
774                     msgq_str(sp, M_INFO, name,
775                         "068|No files named %s, readable by you, to recover");
776                     return (1);
777           }
778           if (found) {
779                     if (requested > 1)
780                               msgq(sp, M_INFO,
781               "069|There are older versions of this file for you to recover");
782                     if (found > requested)
783                               msgq(sp, M_INFO,
784                                   "070|There are other files for you to recover");
785           }
786 
787           /*
788            * Create the FREF structure, start the btree file.
789            *
790            * XXX
791            * file_init() is going to set ep->rcv_path.
792            */
793           if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
794                     free(recp);
795                     free(pathp);
796                     (void)close(sv_fd);
797                     return (1);
798           }
799 
800           /*
801            * We keep an open lock on the file so that the recover option can
802            * distinguish between files that are live and those that need to
803            * be recovered.  The lock is already acquired, just copy it.
804            */
805           ep = sp->ep;
806           ep->rcv_mpath = recp;
807           ep->rcv_fd = sv_fd;
808           if (!locked)
809                     F_SET(frp, FR_UNLOCKED);
810 
811           /* We believe the file is recoverable. */
812           F_SET(ep, F_RCV_ON);
813           free(pathp);
814           return (0);
815 }
816 
817 /*
818  * rcv_copy --
819  *        Copy a recovery file.
820  */
821 static int
rcv_copy(SCR * sp,int wfd,char * fname)822 rcv_copy(SCR *sp, int wfd, char *fname)
823 {
824           int nr, nw, off, rfd;
825           char buf[8 * 1024];
826 
827           if ((rfd = open(fname, O_RDONLY, 0)) == -1)
828                     goto err;
829           while ((nr = read(rfd, buf, sizeof(buf))) > 0)
830                     for (off = 0; nr; nr -= nw, off += nw)
831                               if ((nw = write(wfd, buf + off, nr)) < 0)
832                                         goto err;
833           if (nr == 0)
834                     return (0);
835 
836 err:      msgq_str(sp, M_SYSERR, fname, "%s");
837           return (1);
838 }
839 
840 /*
841  * rcv_gets --
842  *        Fgets(3) for a file descriptor.
843  */
844 static char *
rcv_gets(char * buf,size_t len,int fd)845 rcv_gets(char *buf, size_t len, int fd)
846 {
847           int nr;
848           char *p;
849 
850           if ((nr = read(fd, buf, len - 1)) == -1)
851                     return (NULL);
852           if ((p = strchr(buf, '\n')) == NULL)
853                     return (NULL);
854           (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
855           return (buf);
856 }
857 
858 /*
859  * rcv_mktemp --
860  *        Paranoid make temporary file routine.
861  */
862 static int
rcv_mktemp(SCR * sp,char * path,const char * dname,int perms)863 rcv_mktemp(SCR *sp, char *path, const char *dname, int perms)
864 {
865           int fd;
866 
867           /*
868            * !!!
869            * We expect mkstemp(3) to set the permissions correctly.  On
870            * historic System V systems, mkstemp didn't.  Do it here, on
871            * GP's.
872            *
873            * XXX
874            * The variable perms should really be a mode_t, and it would
875            * be nice to use fchmod(2) instead of chmod(2), here.
876            */
877           if ((fd = mkstemp(path)) == -1)
878                     msgq_str(sp, M_SYSERR, dname, "%s");
879           else
880                     (void)chmod(path, perms);
881           return (fd);
882 }
883 
884 /*
885  * rcv_email --
886  *        Send email.
887  */
888 static void
rcv_email(SCR * sp,int fd)889 rcv_email(SCR *sp, int fd)
890 {
891           struct stat sb;
892           pid_t pid;
893 
894           if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb) == -1) {
895                     msgq_str(sp, M_SYSERR,
896                         _PATH_SENDMAIL, "071|not sending email: %s");
897                     return;
898           }
899 
900           /*
901            * !!!
902            * If you need to port this to a system that doesn't have
903            * sendmail, the -t flag causes sendmail to read the message
904            * for the recipients instead of specifying them some other
905            * way.
906            */
907           switch (pid = fork()) {
908           case -1:                /* Error. */
909                     msgq(sp, M_SYSERR, "fork");
910                     break;
911           case 0:                 /* Sendmail. */
912                     if (lseek(fd, 0, SEEK_SET) == -1) {
913                               msgq(sp, M_SYSERR, "lseek");
914                               _exit(127);
915                     }
916                     if (fd != STDIN_FILENO) {
917                               (void)dup2(fd, STDIN_FILENO);
918                               (void)close(fd);
919                     }
920                     execl(_PATH_SENDMAIL, "sendmail", "-t", NULL);
921                     msgq(sp, M_SYSERR, _PATH_SENDMAIL);
922                     _exit(127);
923           default:                /* Parent. */
924                     while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
925                               continue;
926                     break;
927           }
928 
929 }
930