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