Home | History | Annotate | Line # | Download | only in common
      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
    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
    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
    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
    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
    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
    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
    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
    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 *
    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
    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
    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