head	1.12;
access;
symbols
	perseant-exfatfs-base-20250801:1.12
	perseant-exfatfs-base-20240630:1.12
	perseant-exfatfs:1.12.0.36
	perseant-exfatfs-base:1.12
	cjep_sun2x:1.12.0.34
	cjep_sun2x-base:1.12
	cjep_staticlib_x-base1:1.12
	cjep_staticlib_x:1.12.0.32
	cjep_staticlib_x-base:1.12
	phil-wifi-20200421:1.12
	phil-wifi-20200411:1.12
	phil-wifi-20200406:1.12
	pgoyette-compat-merge-20190127:1.12
	pgoyette-compat-20190127:1.12
	pgoyette-compat-20190118:1.12
	pgoyette-compat-1226:1.12
	pgoyette-compat-1126:1.12
	pgoyette-compat-1020:1.12
	pgoyette-compat-0930:1.12
	pgoyette-compat-0906:1.12
	pgoyette-compat-0728:1.12
	pgoyette-compat-0625:1.12
	pgoyette-compat-0521:1.12
	pgoyette-compat-0502:1.12
	pgoyette-compat-0422:1.12
	pgoyette-compat-0415:1.12
	pgoyette-compat-0407:1.12
	pgoyette-compat-0330:1.12
	pgoyette-compat-0322:1.12
	pgoyette-compat-0315:1.12
	pgoyette-compat:1.12.0.30
	pgoyette-compat-base:1.12
	perseant-stdc-iso10646:1.12.0.28
	perseant-stdc-iso10646-base:1.12
	prg-localcount2-base3:1.12
	prg-localcount2-base2:1.12
	prg-localcount2-base1:1.12
	prg-localcount2:1.12.0.26
	prg-localcount2-base:1.12
	pgoyette-localcount-20170426:1.12
	bouyer-socketcan-base1:1.12
	pgoyette-localcount-20170320:1.12
	bouyer-socketcan:1.12.0.24
	bouyer-socketcan-base:1.12
	pgoyette-localcount-20170107:1.12
	pgoyette-localcount-20161104:1.12
	localcount-20160914:1.12
	pgoyette-localcount-20160806:1.12
	pgoyette-localcount-20160726:1.12
	pgoyette-localcount:1.12.0.22
	pgoyette-localcount-base:1.12
	netbsd-5-2-3-RELEASE:1.12
	netbsd-5-1-5-RELEASE:1.12
	yamt-pagecache-base9:1.12
	yamt-pagecache-tag8:1.12
	tls-earlyentropy:1.12.0.18
	tls-earlyentropy-base:1.12
	riastradh-xf86-video-intel-2-7-1-pre-2-21-15:1.12
	riastradh-drm2-base3:1.12
	netbsd-5-2-2-RELEASE:1.12
	netbsd-5-1-4-RELEASE:1.12
	netbsd-5-2-1-RELEASE:1.12
	netbsd-5-1-3-RELEASE:1.12
	agc-symver:1.12.0.20
	agc-symver-base:1.12
	tls-maxphys-base:1.12
	yamt-pagecache-base8:1.12
	netbsd-5-2:1.12.0.16
	yamt-pagecache-base7:1.12
	netbsd-5-2-RELEASE:1.12
	netbsd-5-2-RC1:1.12
	yamt-pagecache-base6:1.12
	yamt-pagecache-base5:1.12
	yamt-pagecache-base4:1.12
	netbsd-5-1-2-RELEASE:1.12
	netbsd-5-1-1-RELEASE:1.12
	yamt-pagecache-base3:1.12
	yamt-pagecache-base2:1.12
	yamt-pagecache:1.12.0.14
	yamt-pagecache-base:1.12
	bouyer-quota2-nbase:1.12
	bouyer-quota2:1.12.0.12
	bouyer-quota2-base:1.12
	matt-nb5-pq3:1.12.0.10
	matt-nb5-pq3-base:1.12
	netbsd-5-1:1.12.0.8
	netbsd-5-1-RELEASE:1.12
	netbsd-5-1-RC4:1.12
	netbsd-5-1-RC3:1.12
	netbsd-5-1-RC2:1.12
	netbsd-5-1-RC1:1.12
	netbsd-5-0-2-RELEASE:1.12
	netbsd-5-0-1-RELEASE:1.12
	jym-xensuspend-nbase:1.12
	netbsd-5-0:1.12.0.6
	netbsd-5-0-RELEASE:1.12
	netbsd-5-0-RC4:1.12
	netbsd-5-0-RC3:1.12
	netbsd-5-0-RC2:1.12
	jym-xensuspend:1.12.0.4
	jym-xensuspend-base:1.12
	netbsd-5-0-RC1:1.12
	netbsd-5:1.12.0.2
	netbsd-5-base:1.12
	matt-mips64-base2:1.11
	matt-mips64:1.11.0.26
	mjf-devfs2:1.11.0.24
	mjf-devfs2-base:1.11
	netbsd-4-0-1-RELEASE:1.11
	wrstuden-revivesa-base-3:1.11
	wrstuden-revivesa-base-2:1.11
	wrstuden-fixsa-newbase:1.11
	wrstuden-revivesa-base-1:1.11
	yamt-pf42-base4:1.11
	yamt-pf42-base3:1.11
	hpcarm-cleanup-nbase:1.11
	yamt-pf42-baseX:1.11
	yamt-pf42-base2:1.11
	wrstuden-revivesa:1.11.0.22
	wrstuden-revivesa-base:1.11
	yamt-pf42:1.11.0.20
	yamt-pf42-base:1.11
	keiichi-mipv6-nbase:1.11
	keiichi-mipv6:1.11.0.18
	keiichi-mipv6-base:1.11
	matt-armv6-nbase:1.11
	matt-armv6-prevmlocking:1.11
	wrstuden-fixsa-base-1:1.11
	netbsd-4-0:1.11.0.16
	netbsd-4-0-RELEASE:1.11
	cube-autoconf:1.11.0.14
	cube-autoconf-base:1.11
	netbsd-4-0-RC5:1.11
	netbsd-4-0-RC4:1.11
	netbsd-4-0-RC3:1.11
	netbsd-4-0-RC2:1.11
	netbsd-4-0-RC1:1.11
	matt-armv6:1.11.0.12
	matt-armv6-base:1.11
	matt-mips64-base:1.11
	hpcarm-cleanup:1.11.0.10
	hpcarm-cleanup-base:1.11
	netbsd-3-1-1-RELEASE:1.9.2.1
	netbsd-3-0-3-RELEASE:1.9.2.1
	wrstuden-fixsa:1.11.0.8
	wrstuden-fixsa-base:1.11
	abandoned-netbsd-4-base:1.11
	abandoned-netbsd-4:1.11.0.4
	netbsd-3-1:1.9.2.1.0.4
	netbsd-3-1-RELEASE:1.9.2.1
	netbsd-3-0-2-RELEASE:1.9.2.1
	netbsd-3-1-RC4:1.9.2.1
	netbsd-3-1-RC3:1.9.2.1
	netbsd-3-1-RC2:1.9.2.1
	netbsd-3-1-RC1:1.9.2.1
	netbsd-4:1.11.0.6
	netbsd-4-base:1.11
	chap-midi-nbase:1.11
	netbsd-3-0-1-RELEASE:1.9.2.1
	chap-midi:1.11.0.2
	chap-midi-base:1.11
	netbsd-3-0:1.9.2.1.0.2
	netbsd-3-0-RELEASE:1.9.2.1
	netbsd-3-0-RC6:1.9.2.1
	netbsd-3-0-RC5:1.9.2.1
	netbsd-3-0-RC4:1.9.2.1
	netbsd-3-0-RC3:1.9.2.1
	netbsd-3-0-RC2:1.9.2.1
	netbsd-3-0-RC1:1.9.2.1
	netbsd-2-0-3-RELEASE:1.8
	netbsd-2-1:1.8.0.8
	netbsd-2-1-RELEASE:1.8
	netbsd-2-1-RC6:1.8
	netbsd-2-1-RC5:1.8
	netbsd-2-1-RC4:1.8
	netbsd-2-1-RC3:1.8
	netbsd-2-1-RC2:1.8
	netbsd-2-1-RC1:1.8
	netbsd-2-0-2-RELEASE:1.8
	netbsd-3:1.9.0.2
	netbsd-3-base:1.9
	netbsd-2-0-1-RELEASE:1.8
	netbsd-2:1.8.0.6
	netbsd-2-base:1.8
	netbsd-2-0-RELEASE:1.8
	netbsd-2-0-RC5:1.8
	netbsd-2-0-RC4:1.8
	netbsd-2-0-RC3:1.8
	netbsd-2-0-RC2:1.8
	netbsd-2-0-RC1:1.8
	netbsd-2-0:1.8.0.4
	netbsd-2-0-base:1.8
	netbsd-1-6-PATCH002-RELEASE:1.8
	netbsd-1-6-PATCH002:1.8
	netbsd-1-6-PATCH002-RC4:1.8
	netbsd-1-6-PATCH002-RC3:1.8
	netbsd-1-6-PATCH002-RC2:1.8
	netbsd-1-6-PATCH002-RC1:1.8
	netbsd-1-6-PATCH001:1.8
	netbsd-1-6-PATCH001-RELEASE:1.8
	netbsd-1-6-PATCH001-RC3:1.8
	netbsd-1-6-PATCH001-RC2:1.8
	netbsd-1-6-PATCH001-RC1:1.8
	fvdl_fs64_base:1.8
	netbsd-1-6-RELEASE:1.8
	netbsd-1-6-RC3:1.8
	netbsd-1-6-RC2:1.8
	netbsd-1-6-RC1:1.8
	netbsd-1-6:1.8.0.2
	netbsd-1-6-base:1.8
	netbsd-1-5-PATCH003:1.5
	netbsd-1-5-PATCH002:1.5
	netbsd-1-5-PATCH001:1.5
	nvi_1_79:1.1.1.4
	netbsd-1-5-RELEASE:1.5
	netbsd-1-5-BETA2:1.5
	netbsd-1-5-BETA:1.5
	netbsd-1-4-PATCH003:1.5
	netbsd-1-5-ALPHA2:1.5
	netbsd-1-5:1.5.0.10
	netbsd-1-5-base:1.5
	minoura-xpg4dl-base:1.5
	minoura-xpg4dl:1.5.0.8
	netbsd-1-4-PATCH002:1.5
	wrstuden-devbsize-19991221:1.5
	wrstuden-devbsize:1.5.0.6
	wrstuden-devbsize-base:1.5
	comdex-fall-1999:1.5.0.4
	comdex-fall-1999-base:1.5
	netbsd-1-4-PATCH001:1.5
	netbsd-1-4-RELEASE:1.5
	netbsd-1-4:1.5.0.2
	netbsd-1-4-base:1.5
	netbsd-1-3-PATCH003:1.3
	netbsd-1-3-PATCH003-CANDIDATE2:1.3
	netbsd-1-3-PATCH003-CANDIDATE1:1.3
	netbsd-1-3-PATCH003-CANDIDATE0:1.3
	netbsd-1-3-PATCH002:1.3
	netbsd-1-3-PATCH001:1.3
	netbsd-1-3-RELEASE:1.3
	netbsd-1-3-BETA:1.3
	netbsd-1-3:1.3.0.2
	netbsd-1-3-base:1.3
	netbsd-1-2-PATCH001:1.3
	netbsd-1-2-RELEASE:1.3
	netbsd-1-2-BETA:1.3
	netbsd-1-2:1.3.0.4
	netbsd-1-2-base:1.3
	nvi_1_66:1.1.1.3
	BOSTIC:1.1.1
	netbsd-1-1-PATCH001:1.2
	netbsd-1-1-RELEASE:1.2
	netbsd-1-1:1.2.0.2
	netbsd-1-1-base:1.2
	netbsd-1-0-PATCH06:1.1.1.2
	netbsd-1-0-PATCH05:1.1.1.2
	netbsd-1-0-PATCH04:1.1.1.2
	netbsd-1-0-PATCH03:1.1.1.2
	netbsd-1-0-PATCH02:1.1.1.2
	netbsd-1-0-PATCH1:1.1.1.2
	netbsd-1-0-PATCH0:1.1.1.2
	netbsd-1-0-RELEASE:1.1.1.2
	netbsd-1-0:1.1.1.2.0.2
	nvi-1-34b:1.1.1.2
	nvi-1-33b:1.1.1.1
	bostic-nvi:1.1.1;
locks; strict;
comment	@ * @;


1.12
date	2008.10.29.16.49.35;	author christos;	state dead;
branches;
next	1.11;

1.11
date	2005.06.02.04.25.16;	author lukem;	state Exp;
branches;
next	1.10;

1.10
date	2005.06.02.04.03.03;	author lukem;	state Exp;
branches;
next	1.9;

1.9
date	2005.01.19.01.20.24;	author mycroft;	state Exp;
branches
	1.9.2.1;
next	1.8;

1.8
date	2002.04.09.01.47.32;	author thorpej;	state Exp;
branches;
next	1.7;

1.7
date	2001.05.13.12.03.00;	author aymeric;	state Exp;
branches;
next	1.6;

1.6
date	2001.03.31.11.37.46;	author aymeric;	state Exp;
branches;
next	1.5;

1.5
date	98.07.06.07.01.52;	author mrg;	state Exp;
branches;
next	1.4;

1.4
date	98.01.09.08.07.04;	author perry;	state Exp;
branches;
next	1.3;

1.3
date	96.05.20.03.47.10;	author mrg;	state Exp;
branches;
next	1.2;

1.2
date	95.06.23.02.07.16;	author jtc;	state Exp;
branches;
next	1.1;

1.1
date	94.08.17.16.19.05;	author cgd;	state Exp;
branches
	1.1.1.1;
next	;

1.9.2.1
date	2005.06.15.05.27.39;	author snj;	state Exp;
branches;
next	;

1.1.1.1
date	94.08.17.16.19.06;	author cgd;	state Exp;
branches;
next	1.1.1.2;

1.1.1.2
date	94.08.17.19.25.47;	author cgd;	state Exp;
branches
	1.1.1.2.2.1;
next	1.1.1.3;

1.1.1.3
date	96.05.20.02.05.25;	author mrg;	state Exp;
branches;
next	1.1.1.4;

1.1.1.4
date	2001.03.31.11.30.02;	author aymeric;	state Exp;
branches;
next	;

1.1.1.2.2.1
date	94.08.17.19.25.47;	author cgd;	state dead;
branches;
next	1.1.1.2.2.2;

1.1.1.2.2.2
date	94.08.17.19.25.48;	author cgd;	state Exp;
branches;
next	;


desc
@@


1.12
log
@bye old vi!
@
text
@/*	$NetBSD: recover.c,v 1.11 2005/06/02 04:25:16 lukem Exp $	*/

/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
 *
 * See the LICENSE file for redistribution information.
 */

#include "config.h"

#include <sys/cdefs.h>
#ifndef lint
#if 0
static const char sccsid[] = "@@(#)recover.c	10.21 (Berkeley) 9/15/96";
#else
__RCSID("$NetBSD: recover.c,v 1.11 2005/06/02 04:25:16 lukem Exp $");
#endif
#endif /* not lint */

#include <sys/param.h>
#include <sys/types.h>		/* XXX: param.h may not have included types.h */
#include <sys/queue.h>
#include <sys/stat.h>

/*
 * We include <sys/file.h>, because the open #defines were found there
 * on historical systems.  We also include <fcntl.h> because the open(2)
 * #defines are found there on newer systems.
 */
#include <sys/file.h>

#include <bitstring.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "common.h"
#include "pathnames.h"

/*
 * Recovery code.
 *
 * The basic scheme is as follows.  In the EXF structure, we maintain full
 * paths of a b+tree file and a mail recovery file.  The former is the file
 * used as backing store by the DB package.  The latter is the file that
 * contains an email message to be sent to the user if we crash.  The two
 * simple states of recovery are:
 *
 *	+ first starting the edit session:
 *		the b+tree file exists and is mode 700, the mail recovery
 *		file doesn't exist.
 *	+ after the file has been modified:
 *		the b+tree file exists and is mode 600, the mail recovery
 *		file exists, and is exclusively locked.
 *
 * In the EXF structure we maintain a file descriptor that is the locked
 * file descriptor for the mail recovery file.  NOTE: we sometimes have to
 * do locking with fcntl(2).  This is a problem because if you close(2) any
 * file descriptor associated with the file, ALL of the locks go away.  Be
 * sure to remember that if you have to modify the recovery code.  (It has
 * been rhetorically asked of what the designers could have been thinking
 * when they did that interface.  The answer is simple: they weren't.)
 *
 * To find out if a recovery file/backing file pair are in use, try to get
 * a lock on the recovery file.
 *
 * To find out if a backing file can be deleted at boot time, check for an
 * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
 * special stuff into the backing file itself, or correlate the files at
 * boot time, neither of which looks like fun.)  Note also that there's a
 * window between when the file is created and the X bit is set.  It's small,
 * but it's there.  To fix the window, check for 0 length files as well.
 *
 * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
 * this DOES NOT mean that any initialization has been done, only that we
 * haven't yet failed at setting up or doing recovery.
 *
 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
 * If that bit is not set when ending a file session:
 *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
 *	they are unlink(2)'d, and free(3)'d.
 *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
 *
 * The backing b+tree file is set up when a file is first edited, so that
 * the DB package can use it for on-disk caching and/or to snapshot the
 * file.  When the file is first modified, the mail recovery file is created,
 * the backing file permissions are updated, the file is sync(2)'d to disk,
 * and the timer is started.  Then, at RCV_PERIOD second intervals, the
 * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
 * means that the data structures (SCR, EXF, the underlying tree structures)
 * must be consistent when the signal arrives.
 *
 * The recovery mail file contains normal mail headers, with two additions,
 * which occur in THIS order, as the FIRST TWO headers:
 *
 *	X-vi-recover-file: file_name
 *	X-vi-recover-path: recover_path
 *
 * Since newlines delimit the headers, this means that file names cannot have
 * newlines in them, but that's probably okay.  As these files aren't intended
 * to be long-lived, changing their format won't be too painful.
 *
 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
 */

#define	VI_FHEADER	"X-vi-recover-file: "
#define	VI_PHEADER	"X-vi-recover-path: "

static int	 rcv_copy __P((SCR *, int, char *));
static void	 rcv_email __P((SCR *, char *));
static char	*rcv_gets __P((char *, size_t, int));
static int	 rcv_mailfile __P((SCR *, int, char *));
static int	 rcv_mktemp __P((SCR *, char *, char *, int));

/*
 * rcv_tmp --
 *	Build a file name that will be used as the recovery file.
 *
 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
 */
int
rcv_tmp(sp, ep, name)
	SCR *sp;
	EXF *ep;
	char *name;
{
	struct stat sb;
	int fd;
	char *dp, *p, path[MAXPATHLEN];

	/*
	 * !!!
	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
	 *
	 *
	 * If the recovery directory doesn't exist, try and create it.  As
	 * the recovery files are themselves protected from reading/writing
	 * by other than the owner, the worst that can happen is that a user
	 * would have permission to remove other user's recovery files.  If
	 * the sticky bit has the BSD semantics, that too will be impossible.
	 */
	if (opts_empty(sp, O_RECDIR, 0))
		goto err;
	dp = O_STR(sp, O_RECDIR);
	if (stat(dp, &sb)) {
		if (errno != ENOENT || mkdir(dp, 0)) {
			msgq(sp, M_SYSERR, "%s", dp);
			goto err;
		}
		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
	}

	/* Newlines delimit the mail messages. */
	for (p = name; *p; ++p)
		if (*p == '\n') {
			msgq(sp, M_ERR,
		    "055|Files with newlines in the name are unrecoverable");
			goto err;
		}

	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
	if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
		goto err;
	(void)close(fd);

	if ((ep->rcv_path = strdup(path)) == NULL) {
		msgq(sp, M_SYSERR, NULL);
		(void)unlink(path);
err:		msgq(sp, M_ERR,
		    "056|Modifications not recoverable if the session fails");
		return (1);
	}

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);
}

/*
 * rcv_init --
 *	Force the file to be snapshotted for recovery.
 *
 * PUBLIC: int rcv_init __P((SCR *));
 */
int
rcv_init(sp)
	SCR *sp;
{
	EXF *ep;
	recno_t lno;

	ep = sp->ep;

	/* Only do this once. */
	F_CLR(ep, F_FIRSTMODIFY);

	/* If we already know the file isn't recoverable, we're done. */
	if (!F_ISSET(ep, F_RCV_ON))
		return (0);

	/* Turn off recoverability until we figure out if this will work. */
	F_CLR(ep, F_RCV_ON);

	/* Test if we're recovering a file, not editing one. */
	if (ep->rcv_mpath == NULL) {
		/* Build a file to mail to the user. */
		if (rcv_mailfile(sp, 0, NULL))
			goto err;

		/* Force a read of the entire file. */
		if (db_last(sp, &lno))
			goto err;

		/* Turn on a busy message, and sync it to backing store. */
		sp->gp->scr_busy(sp,
		    "057|Copying file for recovery...", BUSY_ON);
		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
			msgq_str(sp, M_SYSERR, ep->rcv_path,
			    "058|Preservation failed: %s");
			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
			goto err;
		}
		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
	}

	/* Turn off the owner execute bit. */
	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);

err:	msgq(sp, M_ERR,
	    "059|Modifications not recoverable if the session fails");
	return (1);
}

/*
 * rcv_sync --
 *	Sync the file, optionally:
 *		flagging the backup file to be preserved
 *		snapshotting the backup file and send email to the user
 *		sending email to the user if the file was modified
 *		ending the file session
 *
 * PUBLIC: int rcv_sync __P((SCR *, u_int));
 */
int
rcv_sync(sp, flags)
	SCR *sp;
	u_int flags;
{
	EXF *ep;
	int fd, rval;
	char *dp, buf[1024];

	/* Make sure that there's something to recover/sync. */
	ep = sp->ep;
	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
		return (0);

	/* Sync the file if it's been modified. */
	if (F_ISSET(ep, F_MODIFIED)) {
		SIGBLOCK;
		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
			msgq_str(sp, M_SYSERR,
			    ep->rcv_path, "060|File backup failed: %s");
			SIGUNBLOCK;
			return (1);
		}
		SIGUNBLOCK;

		/* REQUEST: don't remove backing file on exit. */
		if (LF_ISSET(RCV_PRESERVE))
			F_SET(ep, F_RCV_NORM);

		/* REQUEST: send email. */
		if (LF_ISSET(RCV_EMAIL))
			rcv_email(sp, ep->rcv_mpath);
	}

	/*
	 * !!!
	 * Each time the user exec's :preserve, we have to snapshot all of
	 * the recovery information, i.e. it's like the user re-edited the
	 * file.  We copy the DB(3) backing file, and then create a new mail
	 * recovery file, it's simpler than exiting and reopening all of the
	 * underlying files.
	 *
	 * REQUEST: snapshot the file.
	 */
	rval = 0;
	if (LF_ISSET(RCV_SNAPSHOT)) {
		if (opts_empty(sp, O_RECDIR, 0))
			goto err;
		dp = O_STR(sp, O_RECDIR);
		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
			goto err;
		sp->gp->scr_busy(sp,
		    "061|Copying file for recovery...", BUSY_ON);
		if (rcv_copy(sp, fd, ep->rcv_path) ||
		    close(fd) || rcv_mailfile(sp, 1, buf)) {
			(void)unlink(buf);
			(void)close(fd);
			rval = 1;
		}
		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
	}
	if (0) {
err:		rval = 1;
	}

	/* REQUEST: end the file session. */
	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
		rval = 1;

	return (rval);
}

/*
 * rcv_mailfile --
 *	Build the file to mail to the user.
 */
static int
rcv_mailfile(sp, issync, cp_path)
	SCR *sp;
	int issync;
	char *cp_path;
{
	EXF *ep;
	GS *gp;
	struct passwd *pw;
	size_t len;
	time_t now;
	uid_t uid;
	int fd;
	char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
	char *t1, *t2, *t3;

	/*
	 * XXX
	 * MAXHOSTNAMELEN is in various places on various systems, including
	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
	 */
#ifndef MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN	1024
#endif
	char host[MAXHOSTNAMELEN];

	gp = sp->gp;
	ep = sp->ep;
	if ((pw = getpwuid(uid = getuid())) == NULL) {
		msgq(sp, M_ERR,
		    "062|Information on user id %u not found", uid);
		return (1);
	}

	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	dp = O_STR(sp, O_RECDIR);
	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1 ||
	    fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
		goto err;

	/*
	 * XXX
	 * We keep an open lock on the file so that the recover option can
	 * distinguish between files that are live and those that need to
	 * be recovered.  There's an obvious window between the mkstemp call
	 * and the lock, but it's pretty small.
	 */
	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
	if (!issync) {
		/* Save the recover file descriptor, and mail path. */
		ep->rcv_fd = fd;
		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
			msgq(sp, M_SYSERR, NULL);
			goto err;
		}
		cp_path = ep->rcv_path;
	}

	/*
	 * XXX
	 * We can't use stdio(3) here.  The problem is that we may be using
	 * fcntl(2), so if ANY file descriptor into the file is closed, the
	 * lock is lost.  So, we could never close the FILE *, even if we
	 * dup'd the fd first.
	 */
	t = sp->frp->name;
	if ((p = strrchr(t, '/')) == NULL)
		p = t;
	else
		++p;
	(void)time(&now);
	(void)gethostname(host, sizeof(host));
	len = snprintf(buf, sizeof(buf),
	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
	    VI_FHEADER, t,			/* Non-standard. */
	    VI_PHEADER, cp_path,		/* Non-standard. */
	    "Reply-To: root",
	    "From: root (Vi recovery program)",
	    "To: ", pw->pw_name,
	    "Subject: Vi saved the file ", p,
	    "Precedence: bulk");		/* For vacation(1). */
	if (len > sizeof(buf) - 1)
		goto lerr;
	if (write(fd, buf, len) != len)
		goto werr;

	len = snprintf(buf, sizeof(buf),
	    "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
	    "On ", ctime(&now), ", the user ", pw->pw_name,
	    " was editing a file named ", t, " on the machine ",
	    host, ", when it was saved for recovery. ",
	    "You can recover most, if not all, of the changes ",
	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
	    gp->progname, " -r ", t);
	if (len > sizeof(buf) - 1) {
lerr:		msgq(sp, M_ERR, "064|Recovery file buffer overrun");
		goto err;
	}

	/*
	 * Format the message.  (Yes, I know it's silly.)
	 * Requires that the message end in a <newline>.
	 */
#define	FMTCOLS	60
	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
		/* Check for a short length. */
		if (len <= FMTCOLS) {
			t2 = t1 + (len - 1);
			goto wout;
		}

		/* Check for a required <newline>. */
		t2 = strchr(t1, '\n');
		if (t2 - t1 <= FMTCOLS)
			goto wout;

		/* Find the closest space, if any. */
		for (t3 = t2; t2 > t1; --t2)
			if (*t2 == ' ') {
				if (t2 - t1 <= FMTCOLS)
					goto wout;
				t3 = t2;
			}
		t2 = t3;

		/* t2 points to the last character to display. */
wout:		*t2++ = '\n';

		/* t2 points one after the last character to display. */
		if (write(fd, t1, t2 - t1) != t2 - t1)
			goto werr;
	}

	if (issync) {
		rcv_email(sp, mpath);
		if (close(fd)) {
werr:			msgq(sp, M_SYSERR, "065|Recovery file");
			goto err;
		}
	}
	return (0);

err:	if (!issync)
		ep->rcv_fd = -1;
	if (fd != -1)
		(void)close(fd);
	return (1);
}

/*
 *	people making love
 *	never exactly the same
 *	just like a snowflake
 *
 * rcv_list --
 *	List the files that can be recovered by this user.
 *
 * PUBLIC: int rcv_list __P((SCR *));
 */
int
rcv_list(sp)
	SCR *sp;
{
	struct dirent *dp;
	struct stat sb;
	DIR *dirp;
	FILE *fp;
	int found;
	char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];

	/* Open the recovery directory for reading. */
	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	p = O_STR(sp, O_RECDIR);
	if (chdir(p) || (dirp = opendir(".")) == NULL) {
		msgq_str(sp, M_SYSERR, p, "recdir: %s");
		return (1);
	}

	/* Read the directory. */
	for (found = 0; (dp = readdir(dirp)) != NULL;) {
		if (strncmp(dp->d_name, "recover.", 8))
			continue;

		/*
		 * If it's readable, it's recoverable.
		 *
		 * XXX
		 * Should be "r", we don't want to write the file.  However,
		 * if we're using fcntl(2), there's no way to lock a file
		 * descriptor that's not open for writing.
		 */
		if ((fp = fopen(dp->d_name, "r+")) == NULL)
			continue;

		switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
		case LOCK_FAILED:
			/*
			 * XXX
			 * Assume that a lock can't be acquired, but that we
			 * should permit recovery anyway.  If this is wrong,
			 * and someone else is using the file, we're going to
			 * die horribly.
			 */
			break;
		case LOCK_SUCCESS:
			break;
		case LOCK_UNAVAIL:
			/* If it's locked, it's live. */
			(void)fclose(fp);
			continue;
		}

		/* Check the headers. */
		if (fgets(file, sizeof(file), fp) == NULL ||
		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
		    (p = strchr(file, '\n')) == NULL ||
		    fgets(path, sizeof(path), fp) == NULL ||
		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
		    (t = strchr(path, '\n')) == NULL) {
			msgq_str(sp, M_ERR, dp->d_name,
			    "066|%s: malformed recovery file");
			goto next;
		}
		*p = *t = '\0';

		/*
		 * If the file doesn't exist, it's an orphaned recovery file,
		 * toss it.
		 *
		 * XXX
		 * This can occur if the backup file was deleted and we crashed
		 * before deleting the email file.
		 */
		errno = 0;
		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
		    errno == ENOENT) {
			(void)unlink(dp->d_name);
			goto next;
		}

		/* Get the last modification time and display. */
		(void)fstat(fileno(fp), &sb);
		(void)printf("%.24s: %s\n",
		    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
		found = 1;

		/* Close, discarding lock. */
next:		(void)fclose(fp);
	}
	if (found == 0)
		(void)printf("vi: no files to recover.\n");
	(void)closedir(dirp);
	return (0);
}

/*
 * rcv_read --
 *	Start a recovered file as the file to edit.
 *
 * PUBLIC: int rcv_read __P((SCR *, FREF *));
 */
int
rcv_read(sp, frp)
	SCR *sp;
	FREF *frp;
{
	struct dirent *dp;
	struct stat sb;
	DIR *dirp;
	EXF *ep;
	time_t rec_mtime;
	int fd, found, locked, requested, sv_fd;
	char *name, *p, *t, *rp, *recp, *pathp;
	char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];

	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	rp = O_STR(sp, O_RECDIR);
	if ((dirp = opendir(rp)) == NULL) {
		msgq_str(sp, M_SYSERR, rp, "%s");
		return (1);
	}

	locked = 0;	/* XXXGCC -Wuninitialized */
	name = frp->name;
	sv_fd = -1;
	rec_mtime = 0;
	recp = pathp = NULL;
	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
		if (strncmp(dp->d_name, "recover.", 8))
			continue;
		(void)snprintf(recpath,
		    sizeof(recpath), "%s/%s", rp, dp->d_name);

		/*
		 * If it's readable, it's recoverable.  It would be very
		 * nice to use stdio(3), but, we can't because that would
		 * require closing and then reopening the file so that we
		 * could have a lock and still close the FP.  Another tip
		 * of the hat to fcntl(2).
		 *
		 * XXX
		 * Should be O_RDONLY, we don't want to write it.  However,
		 * if we're using fcntl(2), there's no way to lock a file
		 * descriptor that's not open for writing.
		 */
		if ((fd = open(recpath, O_RDWR, 0)) == -1)
			continue;

		switch (file_lock(sp, NULL, NULL, fd, 1)) {
		case LOCK_FAILED:
			/*
			 * XXX
			 * Assume that a lock can't be acquired, but that we
			 * should permit recovery anyway.  If this is wrong,
			 * and someone else is using the file, we're going to
			 * die horribly.
			 */
			locked = 0;
			break;
		case LOCK_SUCCESS:
			locked = 1;
			break;
		case LOCK_UNAVAIL:
			/* If it's locked, it's live. */
			(void)close(fd);
			continue;
		}

		/* Check the headers. */
		if (rcv_gets(file, sizeof(file), fd) == NULL ||
		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
		    (p = strchr(file, '\n')) == NULL ||
		    rcv_gets(path, sizeof(path), fd) == NULL ||
		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
		    (t = strchr(path, '\n')) == NULL) {
			msgq_str(sp, M_ERR, recpath,
			    "067|%s: malformed recovery file");
			goto next;
		}
		*p = *t = '\0';
		++found;

		/*
		 * If the file doesn't exist, it's an orphaned recovery file,
		 * toss it.
		 *
		 * XXX
		 * This can occur if the backup file was deleted and we crashed
		 * before deleting the email file.
		 */
		errno = 0;
		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
		    errno == ENOENT) {
			(void)unlink(dp->d_name);
			goto next;
		}

		/* Check the file name. */
		if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
			goto next;

		++requested;

		/*
		 * If we've found more than one, take the most recent.
		 *
		 * XXX
		 * Since we're using st_mtime, for portability reasons,
		 * we only get a single second granularity, instead of
		 * getting it right.
		 */
		(void)fstat(fd, &sb);
		if (recp == NULL || rec_mtime < sb.st_mtime) {
			p = recp;
			t = pathp;
			if ((recp = strdup(recpath)) == NULL) {
				msgq(sp, M_SYSERR, NULL);
				recp = p;
				goto next;
			}
			if ((pathp = strdup(path)) == NULL) {
				msgq(sp, M_SYSERR, NULL);
				free(recp);
				recp = p;
				pathp = t;
				goto next;
			}
			if (p != NULL) {
				free(p);
				free(t);
			}
			rec_mtime = sb.st_mtime;
			if (sv_fd != -1)
				(void)close(sv_fd);
			sv_fd = fd;
		} else
next:			(void)close(fd);
	}
	(void)closedir(dirp);

	if (recp == NULL) {
		msgq_str(sp, M_INFO, name,
		    "068|No files named %s, readable by you, to recover");
		return (1);
	}
	if (found) {
		if (requested > 1)
			msgq(sp, M_INFO,
	    "069|There are older versions of this file for you to recover");
		if (found > requested)
			msgq(sp, M_INFO,
			    "070|There are other files for you to recover");
	}

	/*
	 * Create the FREF structure, start the btree file.
	 *
	 * XXX
	 * file_init() is going to set ep->rcv_path.
	 */
	if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
		free(recp);
		free(pathp);
		(void)close(sv_fd);
		return (1);
	}

	/*
	 * We keep an open lock on the file so that the recover option can
	 * distinguish between files that are live and those that need to
	 * be recovered.  The lock is already acquired, just copy it.
	 */
	ep = sp->ep;
	ep->rcv_mpath = recp;
	ep->rcv_fd = sv_fd;
	if (!locked)
		F_SET(frp, FR_UNLOCKED);

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);
}

/*
 * rcv_copy --
 *	Copy a recovery file.
 */
static int
rcv_copy(sp, wfd, fname)
	SCR *sp;
	int wfd;
	char *fname;
{
	int nr, nw, off, rfd;
	char buf[8 * 1024];

	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
		goto err;
	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
		for (off = 0; nr; nr -= nw, off += nw)
			if ((nw = write(wfd, buf + off, nr)) < 0)
				goto err;
	if (nr == 0)
		return (0);

err:	msgq_str(sp, M_SYSERR, fname, "%s");
	return (1);
}

/*
 * rcv_gets --
 *	Fgets(3) for a file descriptor.
 */
static char *
rcv_gets(buf, len, fd)
	char *buf;
	size_t len;
	int fd;
{
	int nr;
	char *p;

	if ((nr = read(fd, buf, len - 1)) == -1)
		return (NULL);
	if ((p = strchr(buf, '\n')) == NULL)
		return (NULL);
	(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
	return (buf);
}

/*
 * rcv_mktemp --
 *	Paranoid make temporary file routine.
 */
static int
rcv_mktemp(sp, path, dname, perms)
	SCR *sp;
	char *path, *dname;
	int perms;
{
	int fd;

	/*
	 * !!!
	 * We expect mkstemp(3) to set the permissions correctly.  On
	 * historic System V systems, mkstemp didn't.  Do it here, on
	 * GP's.
	 *
	 * XXX
	 * The variable perms should really be a mode_t, and it would
	 * be nice to use fchmod(2) instead of chmod(2), here.
	 */
	if ((fd = mkstemp(path)) == -1)
		msgq_str(sp, M_SYSERR, dname, "%s");
	else
		(void)chmod(path, perms);
	return (fd);
}

/*
 * rcv_email --
 *	Send email.
 */
static void
rcv_email(sp, fname)
	SCR *sp;
	char *fname;
{
	struct stat sb;
	char buf[MAXPATHLEN * 2 + 20];

	if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
		msgq_str(sp, M_SYSERR,
		    _PATH_SENDMAIL, "071|not sending email: %s");
	else {
		/*
		 * !!!
		 * If you need to port this to a system that doesn't have
		 * sendmail, the -t flag causes sendmail to read the message
		 * for the recipients instead of specifying them some other
		 * way.
		 */
		(void)snprintf(buf, sizeof(buf),
		    "%s -t < %s", _PATH_SENDMAIL, fname);
		(void)system(buf);
	}
}
@


1.11
log
@appease gcc -Wuninitialized.
Each is marked with XXXGCC, because in some cases it looks like gcc
isn't correctly detecting that
	for ( ; xxx ; yyy)
		foo = 0;
always sets foo ...
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.10 2005/06/02 04:03:03 lukem Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: recover.c,v 1.10 2005/06/02 04:03:03 lukem Exp $");
@


1.10
log
@Don't attempt to dereference an uninitialized 'ep' pointer if we can't
make a temporary file to mail to the user.
Detected with gcc -Wuninitialized.
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.9 2005/01/19 01:20:24 mycroft Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: recover.c,v 1.9 2005/01/19 01:20:24 mycroft Exp $");
d623 1
@


1.9
log
@Set FD_CLOEXEC on the recovery mail file.
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.8 2002/04/09 01:47:32 thorpej Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: recover.c,v 1.8 2002/04/09 01:47:32 thorpej Exp $");
d363 1
a384 1
	ep = sp->ep;
@


1.9.2.1
log
@Pull up revision 1.10 (requested by lukem in ticket #409):
Don't attempt to dereference an uninitialized 'ep' pointer if we can't
make a temporary file to mail to the user.
Detected with gcc -Wuninitialized.
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.9 2005/01/19 01:20:24 mycroft Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD: recover.c,v 1.9 2005/01/19 01:20:24 mycroft Exp $");
a362 1
	ep = sp->ep;
d384 1
@


1.8
log
@Use __RCSID() and __COPYRIGHT().
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.7 2001/05/13 12:03:00 aymeric Exp $	*/
d19 1
a19 1
__RCSID("$NetBSD$");
d373 3
a375 2
	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
		return (1);
@


1.7
log
@ERR -> SYSERR when recdir does not exist.
This results in a more useful message for the user.
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.6 2001/03/31 11:37:46 aymeric Exp $	*/
d14 1
d16 1
d18 3
@


1.6
log
@merge changes after import of nvi 1.79
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.5 1998/07/06 07:01:52 mrg Exp $	*/
d613 1
a613 1
		msgq_str(sp, M_ERR, rp, "%s");
@


1.5
log
@- use an array MAXHOSTNAMELEN+1 size to hold hostnames
- ensure hostname from gethostname() is nul-terminated in all cases
- minor KNF
- use MAXHOSTNAMELEN over various other values/defines
- be safe will buffers that hold hostnames
@
text
@d1 1
a1 1
/*	$NetBSD: recover.c,v 1.4 1998/01/09 08:07:04 perry Exp $	*/
d15 1
a15 1
static const char sccsid[] = "@@(#)recover.c	10.18 (Berkeley) 5/15/96";
a35 1
#include <signal.h>
d39 1
d338 1
d355 1
a355 1
	char host[MAXHOSTNAMELEN + 1];
d357 1
a404 1
	host[sizeof(host) - 1] = '\0';
d419 2
a420 1
	len = snprintf(buf, sizeof(buf), "%s%.24s%s%s%s%s%s%s%s%s%s%s%s\n\n",
d425 2
a426 2
	    "to this file using the -r option to ex or vi:\n\n",
	    "\tvi -r ", t);
@


1.4
log
@RCS Id Police.
@
text
@d1 1
a1 1
/*	$NetBSD$	*/
d354 1
a354 1
	char host[MAXHOSTNAMELEN];
d403 1
@


1.3
log
@merge in nvi 1.66
@
text
@d1 2
@


1.2
log
@Changed recovery email message to refer to ex and vi instead of nex and nvi.
@
text
@d4 2
d7 1
a7 27
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
d10 2
d13 1
a13 1
static char sccsid[] = "@@(#)recover.c	8.74 (Berkeley) 8/17/94";
d17 1
a19 1
#include <sys/time.h>
a27 2
#include <netdb.h>		/* MAXHOSTNAMELEN on some systems. */

a37 1
#include <termios.h>
d40 2
a41 6
#include "compat.h"
#include <db.h>
#include <regex.h>
#include <pathnames.h>

#include "vi.h"
d73 1
a73 1
 * boot time, neither or which looks like fun.)  Note also that there's a
d115 1
a115 1
static int	 rcv_mailfile __P((SCR *, EXF *, int, char *));
d121 2
d135 4
d145 2
d160 1
a160 1
		    "Files with newlines in the name are unrecoverable");
d173 1
a173 1
		    "Modifications not recoverable if the session fails");
d185 2
d189 1
a189 1
rcv_init(sp, ep)
d191 1
a192 1
{
d194 2
a195 1
	int btear;
d210 1
a210 1
		if (rcv_mailfile(sp, ep, 0, NULL))
d214 1
a214 1
		if (file_lline(sp, ep, &lno))
d218 2
a219 2
		btear = F_ISSET(sp, S_EXSILENT) ? 0 :
		    !busy_on(sp, "Copying file for recovery...");
d221 3
a223 4
			msgq(sp, M_ERR, "Preservation failed: %s: %s",
			    ep->rcv_path, strerror(errno));
			if (btear)
				busy_off(sp);
d226 1
a226 9
		if (btear)
			busy_off(sp);
	}

	/* Turn on the recovery timer, if it's not yet running. */
	if (!F_ISSET(sp->gp, G_RECOVER_SET) && rcv_on(sp, ep)) {
err:		msgq(sp, M_ERR,
		    "Modifications not recoverable if the session fails");
		return (1);
d235 4
d248 2
d252 1
a252 1
rcv_sync(sp, ep, flags)
a253 1
	EXF *ep;
d256 2
a257 1
	int btear, fd, rval;
d261 1
d267 1
d270 3
a272 2
			msgq(sp, M_SYSERR,
			    "File backup failed: %s", ep->rcv_path);
d275 1
d298 2
a299 2
		btear = F_ISSET(sp, S_EXSILENT) ? 0 :
		    !busy_on(sp, "Copying file for recovery...");
d303 7
a309 7
			goto e1;
		if (rcv_copy(sp, fd, ep->rcv_path) || close(fd))
			goto e2;
		if (rcv_mailfile(sp, ep, 1, buf)) {
e2:			(void)unlink(buf);
e1:			if (fd != -1)
				(void)close(fd);
d312 4
a315 2
		if (btear)
			busy_off(sp);
d319 1
a319 1
	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, ep, 1))
d330 1
a330 1
rcv_mailfile(sp, ep, issync, cp_path)
a331 1
	EXF *ep;
d335 1
d341 1
a341 1
	char *dp, *p, *t, buf[4096], host[MAXHOSTNAMELEN], mpath[MAXPATHLEN];
d344 10
d355 2
a356 1
		msgq(sp, M_ERR, "Information on user id %u not found", uid);
d360 2
d374 3
a376 2
	if (file_lock(NULL, NULL, fd, 1) != LOCK_SUCCESS)
		msgq(sp, M_SYSERR, "Unable to lock recovery file");
d423 1
a423 1
lerr:		msgq(sp, M_ERR, "recovery file buffer overrun");
d457 8
a464 2
		if (write(fd, t1, t2 - t1) != t2 - t1) {
werr:			msgq(sp, M_SYSERR, "recovery file");
a467 4

	if (issync)
		rcv_email(sp, mpath);

d484 2
d498 6
a503 7
	/*
	 * XXX
	 * Messages aren't yet set up.
	 */
	if (chdir(O_STR(sp, O_RECDIR)) || (dirp = opendir(".")) == NULL) {
		(void)fprintf(stderr,
		    "vi: %s: %s\n", O_STR(sp, O_RECDIR), strerror(errno));
d507 1
d523 1
a523 1
		switch (file_lock(NULL, NULL, fileno(fp), 1)) {
d548 2
a549 2
			msgq(sp, M_ERR,
			    "%s: malformed recovery file", dp->d_name);
d571 2
a572 2
		(void)printf("%s: %s",
		    file + sizeof(VI_FHEADER) - 1, ctime(&sb.st_mtime));
d587 2
d601 1
a601 1
	char *name, *p, *t, *recp, *pathp;
d604 5
a608 3
	if ((dirp = opendir(O_STR(sp, O_RECDIR))) == NULL) {
		msgq(sp, M_ERR,
		    "%s: %s", O_STR(sp, O_RECDIR), strerror(errno));
d619 2
a620 2
		(void)snprintf(recpath, sizeof(recpath),
		    "%s/%s", O_STR(sp, O_RECDIR), dp->d_name);
d637 1
a637 1
		switch (file_lock(NULL, NULL, fd, 1)) {
d664 2
a665 2
			msgq(sp, M_ERR,
			    "%s: malformed recovery file", recpath);
d705 1
a705 2
				msgq(sp, M_ERR,
				    "vi: Error: %s.\n", strerror(errno));
d710 2
a711 3
				msgq(sp, M_ERR,
				    "vi: Error: %s.\n", strerror(errno));
				FREE(recp, strlen(recp) + 1);
d717 2
a718 2
				FREE(p, strlen(p) + 1);
				FREE(t, strlen(t) + 1);
d730 2
a731 2
		msgq(sp, M_INFO,
		    "No files named %s, readable by you, to recover", name);
d737 1
a737 1
		   "There are older versions of this file for you to recover");
d740 1
a740 1
			    "There are other files for you to recover");
d794 1
a794 1
err:	msgq(sp, M_SYSERR, "%s", fname);
d808 1
a808 1
	ssize_t nr;
d842 1
a842 1
		msgq(sp, M_SYSERR, "%s", dname);
d860 3
a862 2
	if (stat(_PATH_SENDMAIL, &sb))
		msgq(sp, M_SYSERR, "not sending email: %s", _PATH_SENDMAIL);
@


1.1
log
@Initial revision
@
text
@d35 1
a35 1
static const char sccsid[] = "@@(#)recover.c	8.73 (Berkeley) 8/17/94";
d407 1
a407 1
	    "From: root (Nvi recovery program)",
d409 1
a409 1
	    "Subject: Nvi saved the file ", p,
d421 2
a422 2
	    "to this file using the -r option to nex or nvi:\n\n",
	    "\tnvi -r ", t);
@


1.1.1.1
log
@new public version of nvi
@
text
@@


1.1.1.2
log
@new public version of nvi
@
text
@d35 1
a35 1
static char sccsid[] = "@@(#)recover.c	8.74 (Berkeley) 8/17/94";
@


1.1.1.2.2.1
log
@file recover.c was added on branch netbsd-1-0 on 1994-08-17 19:25:48 +0000
@
text
@d1 869
@


1.1.1.2.2.2
log
@new public version of nvi
@
text
@a0 869
/*-
 * Copyright (c) 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char sccsid[] = "@@(#)recover.c	8.74 (Berkeley) 8/17/94";
#endif /* not lint */

#include <sys/param.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/time.h>

/*
 * We include <sys/file.h>, because the open #defines were found there
 * on historical systems.  We also include <fcntl.h> because the open(2)
 * #defines are found there on newer systems.
 */
#include <sys/file.h>

#include <netdb.h>		/* MAXHOSTNAMELEN on some systems. */

#include <bitstring.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include "compat.h"
#include <db.h>
#include <regex.h>
#include <pathnames.h>

#include "vi.h"

/*
 * Recovery code.
 *
 * The basic scheme is as follows.  In the EXF structure, we maintain full
 * paths of a b+tree file and a mail recovery file.  The former is the file
 * used as backing store by the DB package.  The latter is the file that
 * contains an email message to be sent to the user if we crash.  The two
 * simple states of recovery are:
 *
 *	+ first starting the edit session:
 *		the b+tree file exists and is mode 700, the mail recovery
 *		file doesn't exist.
 *	+ after the file has been modified:
 *		the b+tree file exists and is mode 600, the mail recovery
 *		file exists, and is exclusively locked.
 *
 * In the EXF structure we maintain a file descriptor that is the locked
 * file descriptor for the mail recovery file.  NOTE: we sometimes have to
 * do locking with fcntl(2).  This is a problem because if you close(2) any
 * file descriptor associated with the file, ALL of the locks go away.  Be
 * sure to remember that if you have to modify the recovery code.  (It has
 * been rhetorically asked of what the designers could have been thinking
 * when they did that interface.  The answer is simple: they weren't.)
 *
 * To find out if a recovery file/backing file pair are in use, try to get
 * a lock on the recovery file.
 *
 * To find out if a backing file can be deleted at boot time, check for an
 * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
 * special stuff into the backing file itself, or correlate the files at
 * boot time, neither or which looks like fun.)  Note also that there's a
 * window between when the file is created and the X bit is set.  It's small,
 * but it's there.  To fix the window, check for 0 length files as well.
 *
 * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
 * this DOES NOT mean that any initialization has been done, only that we
 * haven't yet failed at setting up or doing recovery.
 *
 * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
 * If that bit is not set when ending a file session:
 *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
 *	they are unlink(2)'d, and free(3)'d.
 *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
 *
 * The backing b+tree file is set up when a file is first edited, so that
 * the DB package can use it for on-disk caching and/or to snapshot the
 * file.  When the file is first modified, the mail recovery file is created,
 * the backing file permissions are updated, the file is sync(2)'d to disk,
 * and the timer is started.  Then, at RCV_PERIOD second intervals, the
 * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
 * means that the data structures (SCR, EXF, the underlying tree structures)
 * must be consistent when the signal arrives.
 *
 * The recovery mail file contains normal mail headers, with two additions,
 * which occur in THIS order, as the FIRST TWO headers:
 *
 *	X-vi-recover-file: file_name
 *	X-vi-recover-path: recover_path
 *
 * Since newlines delimit the headers, this means that file names cannot have
 * newlines in them, but that's probably okay.  As these files aren't intended
 * to be long-lived, changing their format won't be too painful.
 *
 * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
 */

#define	VI_FHEADER	"X-vi-recover-file: "
#define	VI_PHEADER	"X-vi-recover-path: "

static int	 rcv_copy __P((SCR *, int, char *));
static void	 rcv_email __P((SCR *, char *));
static char	*rcv_gets __P((char *, size_t, int));
static int	 rcv_mailfile __P((SCR *, EXF *, int, char *));
static int	 rcv_mktemp __P((SCR *, char *, char *, int));

/*
 * rcv_tmp --
 *	Build a file name that will be used as the recovery file.
 */
int
rcv_tmp(sp, ep, name)
	SCR *sp;
	EXF *ep;
	char *name;
{
	struct stat sb;
	int fd;
	char *dp, *p, path[MAXPATHLEN];

	/*
	 * If the recovery directory doesn't exist, try and create it.  As
	 * the recovery files are themselves protected from reading/writing
	 * by other than the owner, the worst that can happen is that a user
	 * would have permission to remove other user's recovery files.  If
	 * the sticky bit has the BSD semantics, that too will be impossible.
	 */
	dp = O_STR(sp, O_RECDIR);
	if (stat(dp, &sb)) {
		if (errno != ENOENT || mkdir(dp, 0)) {
			msgq(sp, M_SYSERR, "%s", dp);
			goto err;
		}
		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
	}

	/* Newlines delimit the mail messages. */
	for (p = name; *p; ++p)
		if (*p == '\n') {
			msgq(sp, M_ERR,
		    "Files with newlines in the name are unrecoverable");
			goto err;
		}

	(void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
	if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
		goto err;
	(void)close(fd);

	if ((ep->rcv_path = strdup(path)) == NULL) {
		msgq(sp, M_SYSERR, NULL);
		(void)unlink(path);
err:		msgq(sp, M_ERR,
		    "Modifications not recoverable if the session fails");
		return (1);
	}

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);
}

/*
 * rcv_init --
 *	Force the file to be snapshotted for recovery.
 */
int
rcv_init(sp, ep)
	SCR *sp;
	EXF *ep;
{
	recno_t lno;
	int btear;

	/* Only do this once. */
	F_CLR(ep, F_FIRSTMODIFY);

	/* If we already know the file isn't recoverable, we're done. */
	if (!F_ISSET(ep, F_RCV_ON))
		return (0);

	/* Turn off recoverability until we figure out if this will work. */
	F_CLR(ep, F_RCV_ON);

	/* Test if we're recovering a file, not editing one. */
	if (ep->rcv_mpath == NULL) {
		/* Build a file to mail to the user. */
		if (rcv_mailfile(sp, ep, 0, NULL))
			goto err;

		/* Force a read of the entire file. */
		if (file_lline(sp, ep, &lno))
			goto err;

		/* Turn on a busy message, and sync it to backing store. */
		btear = F_ISSET(sp, S_EXSILENT) ? 0 :
		    !busy_on(sp, "Copying file for recovery...");
		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
			msgq(sp, M_ERR, "Preservation failed: %s: %s",
			    ep->rcv_path, strerror(errno));
			if (btear)
				busy_off(sp);
			goto err;
		}
		if (btear)
			busy_off(sp);
	}

	/* Turn on the recovery timer, if it's not yet running. */
	if (!F_ISSET(sp->gp, G_RECOVER_SET) && rcv_on(sp, ep)) {
err:		msgq(sp, M_ERR,
		    "Modifications not recoverable if the session fails");
		return (1);
	}

	/* Turn off the owner execute bit. */
	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);
}

/*
 * rcv_sync --
 *	Sync the file, optionally:
 *		flagging the backup file to be preserved
 *		snapshotting the backup file and send email to the user
 *		sending email to the user if the file was modified
 *		ending the file session
 */
int
rcv_sync(sp, ep, flags)
	SCR *sp;
	EXF *ep;
	u_int flags;
{
	int btear, fd, rval;
	char *dp, buf[1024];

	/* Make sure that there's something to recover/sync. */
	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
		return (0);

	/* Sync the file if it's been modified. */
	if (F_ISSET(ep, F_MODIFIED)) {
		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
			msgq(sp, M_SYSERR,
			    "File backup failed: %s", ep->rcv_path);
			return (1);
		}

		/* REQUEST: don't remove backing file on exit. */
		if (LF_ISSET(RCV_PRESERVE))
			F_SET(ep, F_RCV_NORM);

		/* REQUEST: send email. */
		if (LF_ISSET(RCV_EMAIL))
			rcv_email(sp, ep->rcv_mpath);
	}

	/*
	 * !!!
	 * Each time the user exec's :preserve, we have to snapshot all of
	 * the recovery information, i.e. it's like the user re-edited the
	 * file.  We copy the DB(3) backing file, and then create a new mail
	 * recovery file, it's simpler than exiting and reopening all of the
	 * underlying files.
	 *
	 * REQUEST: snapshot the file.
	 */
	rval = 0;
	if (LF_ISSET(RCV_SNAPSHOT)) {
		btear = F_ISSET(sp, S_EXSILENT) ? 0 :
		    !busy_on(sp, "Copying file for recovery...");
		dp = O_STR(sp, O_RECDIR);
		(void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
		if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
			goto e1;
		if (rcv_copy(sp, fd, ep->rcv_path) || close(fd))
			goto e2;
		if (rcv_mailfile(sp, ep, 1, buf)) {
e2:			(void)unlink(buf);
e1:			if (fd != -1)
				(void)close(fd);
			rval = 1;
		}
		if (btear)
			busy_off(sp);
	}

	/* REQUEST: end the file session. */
	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, ep, 1))
		rval = 1;

	return (rval);
}

/*
 * rcv_mailfile --
 *	Build the file to mail to the user.
 */
static int
rcv_mailfile(sp, ep, issync, cp_path)
	SCR *sp;
	EXF *ep;
	int issync;
	char *cp_path;
{
	struct passwd *pw;
	size_t len;
	time_t now;
	uid_t uid;
	int fd;
	char *dp, *p, *t, buf[4096], host[MAXHOSTNAMELEN], mpath[MAXPATHLEN];
	char *t1, *t2, *t3;

	if ((pw = getpwuid(uid = getuid())) == NULL) {
		msgq(sp, M_ERR, "Information on user id %u not found", uid);
		return (1);
	}

	dp = O_STR(sp, O_RECDIR);
	(void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
	if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
		return (1);

	/*
	 * XXX
	 * We keep an open lock on the file so that the recover option can
	 * distinguish between files that are live and those that need to
	 * be recovered.  There's an obvious window between the mkstemp call
	 * and the lock, but it's pretty small.
	 */
	if (file_lock(NULL, NULL, fd, 1) != LOCK_SUCCESS)
		msgq(sp, M_SYSERR, "Unable to lock recovery file");
	if (!issync) {
		/* Save the recover file descriptor, and mail path. */
		ep->rcv_fd = fd;
		if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
			msgq(sp, M_SYSERR, NULL);
			goto err;
		}
		cp_path = ep->rcv_path;
	}

	/*
	 * XXX
	 * We can't use stdio(3) here.  The problem is that we may be using
	 * fcntl(2), so if ANY file descriptor into the file is closed, the
	 * lock is lost.  So, we could never close the FILE *, even if we
	 * dup'd the fd first.
	 */
	t = sp->frp->name;
	if ((p = strrchr(t, '/')) == NULL)
		p = t;
	else
		++p;
	(void)time(&now);
	(void)gethostname(host, sizeof(host));
	len = snprintf(buf, sizeof(buf),
	    "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
	    VI_FHEADER, t,			/* Non-standard. */
	    VI_PHEADER, cp_path,		/* Non-standard. */
	    "Reply-To: root",
	    "From: root (Nvi recovery program)",
	    "To: ", pw->pw_name,
	    "Subject: Nvi saved the file ", p,
	    "Precedence: bulk");		/* For vacation(1). */
	if (len > sizeof(buf) - 1)
		goto lerr;
	if (write(fd, buf, len) != len)
		goto werr;

	len = snprintf(buf, sizeof(buf), "%s%.24s%s%s%s%s%s%s%s%s%s%s%s\n\n",
	    "On ", ctime(&now), ", the user ", pw->pw_name,
	    " was editing a file named ", t, " on the machine ",
	    host, ", when it was saved for recovery. ",
	    "You can recover most, if not all, of the changes ",
	    "to this file using the -r option to nex or nvi:\n\n",
	    "\tnvi -r ", t);
	if (len > sizeof(buf) - 1) {
lerr:		msgq(sp, M_ERR, "recovery file buffer overrun");
		goto err;
	}

	/*
	 * Format the message.  (Yes, I know it's silly.)
	 * Requires that the message end in a <newline>.
	 */
#define	FMTCOLS	60
	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
		/* Check for a short length. */
		if (len <= FMTCOLS) {
			t2 = t1 + (len - 1);
			goto wout;
		}

		/* Check for a required <newline>. */
		t2 = strchr(t1, '\n');
		if (t2 - t1 <= FMTCOLS)
			goto wout;

		/* Find the closest space, if any. */
		for (t3 = t2; t2 > t1; --t2)
			if (*t2 == ' ') {
				if (t2 - t1 <= FMTCOLS)
					goto wout;
				t3 = t2;
			}
		t2 = t3;

		/* t2 points to the last character to display. */
wout:		*t2++ = '\n';

		/* t2 points one after the last character to display. */
		if (write(fd, t1, t2 - t1) != t2 - t1) {
werr:			msgq(sp, M_SYSERR, "recovery file");
			goto err;
		}
	}

	if (issync)
		rcv_email(sp, mpath);

	return (0);

err:	if (!issync)
		ep->rcv_fd = -1;
	if (fd != -1)
		(void)close(fd);
	return (1);
}

/*
 *	people making love
 *	never exactly the same
 *	just like a snowflake
 *
 * rcv_list --
 *	List the files that can be recovered by this user.
 */
int
rcv_list(sp)
	SCR *sp;
{
	struct dirent *dp;
	struct stat sb;
	DIR *dirp;
	FILE *fp;
	int found;
	char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];

	/*
	 * XXX
	 * Messages aren't yet set up.
	 */
	if (chdir(O_STR(sp, O_RECDIR)) || (dirp = opendir(".")) == NULL) {
		(void)fprintf(stderr,
		    "vi: %s: %s\n", O_STR(sp, O_RECDIR), strerror(errno));
		return (1);
	}

	for (found = 0; (dp = readdir(dirp)) != NULL;) {
		if (strncmp(dp->d_name, "recover.", 8))
			continue;

		/*
		 * If it's readable, it's recoverable.
		 *
		 * XXX
		 * Should be "r", we don't want to write the file.  However,
		 * if we're using fcntl(2), there's no way to lock a file
		 * descriptor that's not open for writing.
		 */
		if ((fp = fopen(dp->d_name, "r+")) == NULL)
			continue;

		switch (file_lock(NULL, NULL, fileno(fp), 1)) {
		case LOCK_FAILED:
			/*
			 * XXX
			 * Assume that a lock can't be acquired, but that we
			 * should permit recovery anyway.  If this is wrong,
			 * and someone else is using the file, we're going to
			 * die horribly.
			 */
			break;
		case LOCK_SUCCESS:
			break;
		case LOCK_UNAVAIL:
			/* If it's locked, it's live. */
			(void)fclose(fp);
			continue;
		}

		/* Check the headers. */
		if (fgets(file, sizeof(file), fp) == NULL ||
		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
		    (p = strchr(file, '\n')) == NULL ||
		    fgets(path, sizeof(path), fp) == NULL ||
		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
		    (t = strchr(path, '\n')) == NULL) {
			msgq(sp, M_ERR,
			    "%s: malformed recovery file", dp->d_name);
			goto next;
		}
		*p = *t = '\0';

		/*
		 * If the file doesn't exist, it's an orphaned recovery file,
		 * toss it.
		 *
		 * XXX
		 * This can occur if the backup file was deleted and we crashed
		 * before deleting the email file.
		 */
		errno = 0;
		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
		    errno == ENOENT) {
			(void)unlink(dp->d_name);
			goto next;
		}

		/* Get the last modification time and display. */
		(void)fstat(fileno(fp), &sb);
		(void)printf("%s: %s",
		    file + sizeof(VI_FHEADER) - 1, ctime(&sb.st_mtime));
		found = 1;

		/* Close, discarding lock. */
next:		(void)fclose(fp);
	}
	if (found == 0)
		(void)printf("vi: no files to recover.\n");
	(void)closedir(dirp);
	return (0);
}

/*
 * rcv_read --
 *	Start a recovered file as the file to edit.
 */
int
rcv_read(sp, frp)
	SCR *sp;
	FREF *frp;
{
	struct dirent *dp;
	struct stat sb;
	DIR *dirp;
	EXF *ep;
	time_t rec_mtime;
	int fd, found, locked, requested, sv_fd;
	char *name, *p, *t, *recp, *pathp;
	char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];

	if ((dirp = opendir(O_STR(sp, O_RECDIR))) == NULL) {
		msgq(sp, M_ERR,
		    "%s: %s", O_STR(sp, O_RECDIR), strerror(errno));
		return (1);
	}

	name = frp->name;
	sv_fd = -1;
	rec_mtime = 0;
	recp = pathp = NULL;
	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
		if (strncmp(dp->d_name, "recover.", 8))
			continue;
		(void)snprintf(recpath, sizeof(recpath),
		    "%s/%s", O_STR(sp, O_RECDIR), dp->d_name);

		/*
		 * If it's readable, it's recoverable.  It would be very
		 * nice to use stdio(3), but, we can't because that would
		 * require closing and then reopening the file so that we
		 * could have a lock and still close the FP.  Another tip
		 * of the hat to fcntl(2).
		 *
		 * XXX
		 * Should be O_RDONLY, we don't want to write it.  However,
		 * if we're using fcntl(2), there's no way to lock a file
		 * descriptor that's not open for writing.
		 */
		if ((fd = open(recpath, O_RDWR, 0)) == -1)
			continue;

		switch (file_lock(NULL, NULL, fd, 1)) {
		case LOCK_FAILED:
			/*
			 * XXX
			 * Assume that a lock can't be acquired, but that we
			 * should permit recovery anyway.  If this is wrong,
			 * and someone else is using the file, we're going to
			 * die horribly.
			 */
			locked = 0;
			break;
		case LOCK_SUCCESS:
			locked = 1;
			break;
		case LOCK_UNAVAIL:
			/* If it's locked, it's live. */
			(void)close(fd);
			continue;
		}

		/* Check the headers. */
		if (rcv_gets(file, sizeof(file), fd) == NULL ||
		    strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
		    (p = strchr(file, '\n')) == NULL ||
		    rcv_gets(path, sizeof(path), fd) == NULL ||
		    strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
		    (t = strchr(path, '\n')) == NULL) {
			msgq(sp, M_ERR,
			    "%s: malformed recovery file", recpath);
			goto next;
		}
		*p = *t = '\0';
		++found;

		/*
		 * If the file doesn't exist, it's an orphaned recovery file,
		 * toss it.
		 *
		 * XXX
		 * This can occur if the backup file was deleted and we crashed
		 * before deleting the email file.
		 */
		errno = 0;
		if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
		    errno == ENOENT) {
			(void)unlink(dp->d_name);
			goto next;
		}

		/* Check the file name. */
		if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
			goto next;

		++requested;

		/*
		 * If we've found more than one, take the most recent.
		 *
		 * XXX
		 * Since we're using st_mtime, for portability reasons,
		 * we only get a single second granularity, instead of
		 * getting it right.
		 */
		(void)fstat(fd, &sb);
		if (recp == NULL || rec_mtime < sb.st_mtime) {
			p = recp;
			t = pathp;
			if ((recp = strdup(recpath)) == NULL) {
				msgq(sp, M_ERR,
				    "vi: Error: %s.\n", strerror(errno));
				recp = p;
				goto next;
			}
			if ((pathp = strdup(path)) == NULL) {
				msgq(sp, M_ERR,
				    "vi: Error: %s.\n", strerror(errno));
				FREE(recp, strlen(recp) + 1);
				recp = p;
				pathp = t;
				goto next;
			}
			if (p != NULL) {
				FREE(p, strlen(p) + 1);
				FREE(t, strlen(t) + 1);
			}
			rec_mtime = sb.st_mtime;
			if (sv_fd != -1)
				(void)close(sv_fd);
			sv_fd = fd;
		} else
next:			(void)close(fd);
	}
	(void)closedir(dirp);

	if (recp == NULL) {
		msgq(sp, M_INFO,
		    "No files named %s, readable by you, to recover", name);
		return (1);
	}
	if (found) {
		if (requested > 1)
			msgq(sp, M_INFO,
		   "There are older versions of this file for you to recover");
		if (found > requested)
			msgq(sp, M_INFO,
			    "There are other files for you to recover");
	}

	/*
	 * Create the FREF structure, start the btree file.
	 *
	 * XXX
	 * file_init() is going to set ep->rcv_path.
	 */
	if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
		free(recp);
		free(pathp);
		(void)close(sv_fd);
		return (1);
	}

	/*
	 * We keep an open lock on the file so that the recover option can
	 * distinguish between files that are live and those that need to
	 * be recovered.  The lock is already acquired, just copy it.
	 */
	ep = sp->ep;
	ep->rcv_mpath = recp;
	ep->rcv_fd = sv_fd;
	if (!locked)
		F_SET(frp, FR_UNLOCKED);

	/* We believe the file is recoverable. */
	F_SET(ep, F_RCV_ON);
	return (0);
}

/*
 * rcv_copy --
 *	Copy a recovery file.
 */
static int
rcv_copy(sp, wfd, fname)
	SCR *sp;
	int wfd;
	char *fname;
{
	int nr, nw, off, rfd;
	char buf[8 * 1024];

	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
		goto err;
	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
		for (off = 0; nr; nr -= nw, off += nw)
			if ((nw = write(wfd, buf + off, nr)) < 0)
				goto err;
	if (nr == 0)
		return (0);

err:	msgq(sp, M_SYSERR, "%s", fname);
	return (1);
}

/*
 * rcv_gets --
 *	Fgets(3) for a file descriptor.
 */
static char *
rcv_gets(buf, len, fd)
	char *buf;
	size_t len;
	int fd;
{
	ssize_t nr;
	char *p;

	if ((nr = read(fd, buf, len - 1)) == -1)
		return (NULL);
	if ((p = strchr(buf, '\n')) == NULL)
		return (NULL);
	(void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
	return (buf);
}

/*
 * rcv_mktemp --
 *	Paranoid make temporary file routine.
 */
static int
rcv_mktemp(sp, path, dname, perms)
	SCR *sp;
	char *path, *dname;
	int perms;
{
	int fd;

	/*
	 * !!!
	 * We expect mkstemp(3) to set the permissions correctly.  On
	 * historic System V systems, mkstemp didn't.  Do it here, on
	 * GP's.
	 *
	 * XXX
	 * The variable perms should really be a mode_t, and it would
	 * be nice to use fchmod(2) instead of chmod(2), here.
	 */
	if ((fd = mkstemp(path)) == -1)
		msgq(sp, M_SYSERR, "%s", dname);
	else
		(void)chmod(path, perms);
	return (fd);
}

/*
 * rcv_email --
 *	Send email.
 */
static void
rcv_email(sp, fname)
	SCR *sp;
	char *fname;
{
	struct stat sb;
	char buf[MAXPATHLEN * 2 + 20];

	if (stat(_PATH_SENDMAIL, &sb))
		msgq(sp, M_SYSERR, "not sending email: %s", _PATH_SENDMAIL);
	else {
		/*
		 * !!!
		 * If you need to port this to a system that doesn't have
		 * sendmail, the -t flag causes sendmail to read the message
		 * for the recipients instead of specifying them some other
		 * way.
		 */
		(void)snprintf(buf, sizeof(buf),
		    "%s -t < %s", _PATH_SENDMAIL, fname);
		(void)system(buf);
	}
}
@


1.1.1.3
log
@import of nvi 1.66
@
text
@a3 2
 * Copyright (c) 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
d5 27
a31 1
 * See the LICENSE file for redistribution information.
a33 2
#include "config.h"

d35 1
a35 1
static const char sccsid[] = "@@(#)recover.c	10.18 (Berkeley) 5/15/96";
a38 1
#include <sys/types.h>		/* XXX: param.h may not have included types.h */
d41 1
d50 2
d62 1
d65 6
a70 2
#include "common.h"
#include "pathnames.h"
d102 1
a102 1
 * boot time, neither of which looks like fun.)  Note also that there's a
d144 1
a144 1
static int	 rcv_mailfile __P((SCR *, int, char *));
a149 2
 *
 * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
a161 4
	 * !!!
	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
	 *
	 *
a167 2
	if (opts_empty(sp, O_RECDIR, 0))
		goto err;
d181 1
a181 1
		    "055|Files with newlines in the name are unrecoverable");
d194 1
a194 1
		    "056|Modifications not recoverable if the session fails");
a205 2
 *
 * PUBLIC: int rcv_init __P((SCR *));
d208 1
a208 1
rcv_init(sp)
d210 1
a211 1
	EXF *ep;
d213 1
a213 2

	ep = sp->ep;
d228 1
a228 1
		if (rcv_mailfile(sp, 0, NULL))
d232 1
a232 1
		if (db_last(sp, &lno))
d236 2
a237 2
		sp->gp->scr_busy(sp,
		    "057|Copying file for recovery...", BUSY_ON);
d239 4
a242 3
			msgq_str(sp, M_SYSERR, ep->rcv_path,
			    "058|Preservation failed: %s");
			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
d245 9
a253 1
		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
a261 4

err:	msgq(sp, M_ERR,
	    "059|Modifications not recoverable if the session fails");
	return (1);
a270 2
 *
 * PUBLIC: int rcv_sync __P((SCR *, u_int));
d273 1
a273 1
rcv_sync(sp, flags)
d275 1
d278 1
a278 2
	EXF *ep;
	int fd, rval;
a281 1
	ep = sp->ep;
a286 1
		SIGBLOCK;
d289 2
a290 3
			msgq_str(sp, M_SYSERR,
			    ep->rcv_path, "060|File backup failed: %s");
			SIGUNBLOCK;
a292 1
		SIGUNBLOCK;
d315 2
a316 2
		if (opts_empty(sp, O_RECDIR, 0))
			goto err;
d320 7
a326 7
			goto err;
		sp->gp->scr_busy(sp,
		    "061|Copying file for recovery...", BUSY_ON);
		if (rcv_copy(sp, fd, ep->rcv_path) ||
		    close(fd) || rcv_mailfile(sp, 1, buf)) {
			(void)unlink(buf);
			(void)close(fd);
d329 2
a330 4
		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
	}
	if (0) {
err:		rval = 1;
d334 1
a334 1
	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
d345 1
a345 1
rcv_mailfile(sp, issync, cp_path)
d347 1
a350 1
	EXF *ep;
d356 1
a356 1
	char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
a358 10
	/*
	 * XXX
	 * MAXHOSTNAMELEN is in various places on various systems, including
	 * <netdb.h> and <sys/socket.h>.  If not found, use a large default.
	 */
#ifndef MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN	1024
#endif
	char host[MAXHOSTNAMELEN];

d360 1
a360 2
		msgq(sp, M_ERR,
		    "062|Information on user id %u not found", uid);
a363 2
	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
d376 2
a377 3
	ep = sp->ep;
	if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
d424 1
a424 1
lerr:		msgq(sp, M_ERR, "064|Recovery file buffer overrun");
d458 4
a461 2
		if (write(fd, t1, t2 - t1) != t2 - t1)
			goto werr;
d464 1
a464 1
	if (issync) {
d466 1
a466 5
		if (close(fd)) {
werr:			msgq(sp, M_SYSERR, "065|Recovery file");
			goto err;
		}
	}
a482 2
 *
 * PUBLIC: int rcv_list __P((SCR *));
d495 7
a501 6
	/* Open the recovery directory for reading. */
	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	p = O_STR(sp, O_RECDIR);
	if (chdir(p) || (dirp = opendir(".")) == NULL) {
		msgq_str(sp, M_SYSERR, p, "recdir: %s");
a504 1
	/* Read the directory. */
d520 1
a520 1
		switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
d545 2
a546 2
			msgq_str(sp, M_ERR, dp->d_name,
			    "066|%s: malformed recovery file");
d568 2
a569 2
		(void)printf("%.24s: %s\n",
		    ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
a583 2
 *
 * PUBLIC: int rcv_read __P((SCR *, FREF *));
d596 1
a596 1
	char *name, *p, *t, *rp, *recp, *pathp;
d599 3
a601 5
	if (opts_empty(sp, O_RECDIR, 0))
		return (1);
	rp = O_STR(sp, O_RECDIR);
	if ((dirp = opendir(rp)) == NULL) {
		msgq_str(sp, M_ERR, rp, "%s");
d612 2
a613 2
		(void)snprintf(recpath,
		    sizeof(recpath), "%s/%s", rp, dp->d_name);
d630 1
a630 1
		switch (file_lock(sp, NULL, NULL, fd, 1)) {
d657 2
a658 2
			msgq_str(sp, M_ERR, recpath,
			    "067|%s: malformed recovery file");
d698 2
a699 1
				msgq(sp, M_SYSERR, NULL);
d704 3
a706 2
				msgq(sp, M_SYSERR, NULL);
				free(recp);
d712 2
a713 2
				free(p);
				free(t);
d725 2
a726 2
		msgq_str(sp, M_INFO, name,
		    "068|No files named %s, readable by you, to recover");
d732 1
a732 1
	    "069|There are older versions of this file for you to recover");
d735 1
a735 1
			    "070|There are other files for you to recover");
d789 1
a789 1
err:	msgq_str(sp, M_SYSERR, fname, "%s");
d803 1
a803 1
	int nr;
d837 1
a837 1
		msgq_str(sp, M_SYSERR, dname, "%s");
d855 2
a856 3
	if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
		msgq_str(sp, M_SYSERR,
		    _PATH_SENDMAIL, "071|not sending email: %s");
@


1.1.1.4
log
@import of nvi 1.79
@
text
@d13 1
a13 1
static const char sccsid[] = "@@(#)recover.c	10.21 (Berkeley) 9/15/96";
d34 1
a37 1
#include <time.h>
a335 1
	GS *gp;
a353 1
	gp = sp->gp;
d415 1
a415 2
	len = snprintf(buf, sizeof(buf),
	    "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
d420 2
a421 2
	    "to this file using the -r option to ", gp->progname, ":\n\n\t",
	    gp->progname, " -r ", t);
@

