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