xref: /netbsd-src/external/bsd/nvi/dist/common/recover.c (revision 874da4738d95d84f12cf7d5542973afaad294769)
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