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