xref: /freebsd/contrib/nvi/common/recover.c (revision 6680e5a52f8abf059bbbd3e0be66d9dce476cdf9)
1b8ba871bSPeter Wemm /*-
2b8ba871bSPeter Wemm  * Copyright (c) 1993, 1994
3b8ba871bSPeter Wemm  *	The Regents of the University of California.  All rights reserved.
4b8ba871bSPeter Wemm  * Copyright (c) 1993, 1994, 1995, 1996
5b8ba871bSPeter Wemm  *	Keith Bostic.  All rights reserved.
6b8ba871bSPeter Wemm  *
7b8ba871bSPeter Wemm  * See the LICENSE file for redistribution information.
8b8ba871bSPeter Wemm  */
9b8ba871bSPeter Wemm 
10b8ba871bSPeter Wemm #include "config.h"
11b8ba871bSPeter Wemm 
12f0957ccaSPeter Wemm #include <sys/types.h>
13b8ba871bSPeter Wemm #include <sys/queue.h>
14b8ba871bSPeter Wemm #include <sys/stat.h>
15b8ba871bSPeter Wemm 
16b8ba871bSPeter Wemm /*
17b8ba871bSPeter Wemm  * We include <sys/file.h>, because the open #defines were found there
18b8ba871bSPeter Wemm  * on historical systems.  We also include <fcntl.h> because the open(2)
19b8ba871bSPeter Wemm  * #defines are found there on newer systems.
20b8ba871bSPeter Wemm  */
21b8ba871bSPeter Wemm #include <sys/file.h>
22b8ba871bSPeter Wemm 
23b8ba871bSPeter Wemm #include <bitstring.h>
24b8ba871bSPeter Wemm #include <dirent.h>
25b8ba871bSPeter Wemm #include <errno.h>
26b8ba871bSPeter Wemm #include <fcntl.h>
27b8ba871bSPeter Wemm #include <limits.h>
28b8ba871bSPeter Wemm #include <pwd.h>
29f0957ccaSPeter Wemm #include <netinet/in.h>		/* Required by resolv.h. */
30f0957ccaSPeter Wemm #include <resolv.h>
31b8ba871bSPeter Wemm #include <stdio.h>
32b8ba871bSPeter Wemm #include <stdlib.h>
33b8ba871bSPeter Wemm #include <string.h>
34b8ba871bSPeter Wemm #include <time.h>
35b8ba871bSPeter Wemm #include <unistd.h>
36b8ba871bSPeter Wemm 
37f0957ccaSPeter Wemm #include "../ex/version.h"
38b8ba871bSPeter Wemm #include "common.h"
39b8ba871bSPeter Wemm #include "pathnames.h"
40b8ba871bSPeter Wemm 
41b8ba871bSPeter Wemm /*
42b8ba871bSPeter Wemm  * Recovery code.
43b8ba871bSPeter Wemm  *
44b8ba871bSPeter Wemm  * The basic scheme is as follows.  In the EXF structure, we maintain full
45b8ba871bSPeter Wemm  * paths of a b+tree file and a mail recovery file.  The former is the file
46b8ba871bSPeter Wemm  * used as backing store by the DB package.  The latter is the file that
47b8ba871bSPeter Wemm  * contains an email message to be sent to the user if we crash.  The two
48b8ba871bSPeter Wemm  * simple states of recovery are:
49b8ba871bSPeter Wemm  *
50b8ba871bSPeter Wemm  *	+ first starting the edit session:
51b8ba871bSPeter Wemm  *		the b+tree file exists and is mode 700, the mail recovery
52b8ba871bSPeter Wemm  *		file doesn't exist.
53b8ba871bSPeter Wemm  *	+ after the file has been modified:
54b8ba871bSPeter Wemm  *		the b+tree file exists and is mode 600, the mail recovery
55b8ba871bSPeter Wemm  *		file exists, and is exclusively locked.
56b8ba871bSPeter Wemm  *
57b8ba871bSPeter Wemm  * In the EXF structure we maintain a file descriptor that is the locked
58f0957ccaSPeter Wemm  * file descriptor for the mail recovery file.
59b8ba871bSPeter Wemm  *
60b8ba871bSPeter Wemm  * To find out if a recovery file/backing file pair are in use, try to get
61b8ba871bSPeter Wemm  * a lock on the recovery file.
62b8ba871bSPeter Wemm  *
63b8ba871bSPeter Wemm  * To find out if a backing file can be deleted at boot time, check for an
64b8ba871bSPeter Wemm  * owner execute bit.  (Yes, I know it's ugly, but it's either that or put
65b8ba871bSPeter Wemm  * special stuff into the backing file itself, or correlate the files at
66b8ba871bSPeter Wemm  * boot time, neither of which looks like fun.)  Note also that there's a
67b8ba871bSPeter Wemm  * window between when the file is created and the X bit is set.  It's small,
68b8ba871bSPeter Wemm  * but it's there.  To fix the window, check for 0 length files as well.
69b8ba871bSPeter Wemm  *
70b8ba871bSPeter Wemm  * To find out if a file can be recovered, check the F_RCV_ON bit.  Note,
71b8ba871bSPeter Wemm  * this DOES NOT mean that any initialization has been done, only that we
72b8ba871bSPeter Wemm  * haven't yet failed at setting up or doing recovery.
73b8ba871bSPeter Wemm  *
74b8ba871bSPeter Wemm  * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
75b8ba871bSPeter Wemm  * If that bit is not set when ending a file session:
76b8ba871bSPeter Wemm  *	If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
77b8ba871bSPeter Wemm  *	they are unlink(2)'d, and free(3)'d.
78b8ba871bSPeter Wemm  *	If the EXF file descriptor (rcv_fd) is not -1, it is closed.
79b8ba871bSPeter Wemm  *
80b8ba871bSPeter Wemm  * The backing b+tree file is set up when a file is first edited, so that
81b8ba871bSPeter Wemm  * the DB package can use it for on-disk caching and/or to snapshot the
82b8ba871bSPeter Wemm  * file.  When the file is first modified, the mail recovery file is created,
83b8ba871bSPeter Wemm  * the backing file permissions are updated, the file is sync(2)'d to disk,
84b8ba871bSPeter Wemm  * and the timer is started.  Then, at RCV_PERIOD second intervals, the
85b8ba871bSPeter Wemm  * b+tree file is synced to disk.  RCV_PERIOD is measured using SIGALRM, which
86b8ba871bSPeter Wemm  * means that the data structures (SCR, EXF, the underlying tree structures)
87b8ba871bSPeter Wemm  * must be consistent when the signal arrives.
88b8ba871bSPeter Wemm  *
89f0957ccaSPeter Wemm  * The recovery mail file contains normal mail headers, with two additional
90b8ba871bSPeter Wemm  *
91f0957ccaSPeter Wemm  *	X-vi-data: <file|path>;<base64 encoded path>
92b8ba871bSPeter Wemm  *
93f0957ccaSPeter Wemm  * MIME headers; the folding character is limited to ' '.
94b8ba871bSPeter Wemm  *
95f0957ccaSPeter Wemm  * Btree files are named "vi.XXXXXX" and recovery files are named
96f0957ccaSPeter Wemm  * "recover.XXXXXX".
97b8ba871bSPeter Wemm  */
98b8ba871bSPeter Wemm 
99f0957ccaSPeter Wemm #define	VI_DHEADER	"X-vi-data:"
100b8ba871bSPeter Wemm 
101c271fa92SBaptiste Daroussin static int	 rcv_copy(SCR *, int, char *);
102c271fa92SBaptiste Daroussin static void	 rcv_email(SCR *, char *);
103c271fa92SBaptiste Daroussin static int	 rcv_mailfile(SCR *, int, char *);
104c271fa92SBaptiste Daroussin static int	 rcv_mktemp(SCR *, char *, char *);
105c271fa92SBaptiste Daroussin static int	 rcv_dlnwrite(SCR *, const char *, const char *, FILE *);
106c271fa92SBaptiste Daroussin static int	 rcv_dlnread(SCR *, char **, char **, FILE *);
107b8ba871bSPeter Wemm 
108b8ba871bSPeter Wemm /*
109b8ba871bSPeter Wemm  * rcv_tmp --
110b8ba871bSPeter Wemm  *	Build a file name that will be used as the recovery file.
111b8ba871bSPeter Wemm  *
112c271fa92SBaptiste Daroussin  * PUBLIC: int rcv_tmp(SCR *, EXF *, char *);
113b8ba871bSPeter Wemm  */
114b8ba871bSPeter Wemm int
rcv_tmp(SCR * sp,EXF * ep,char * name)115110d525eSBaptiste Daroussin rcv_tmp(SCR *sp, EXF *ep, char *name)
116b8ba871bSPeter Wemm {
117b8ba871bSPeter Wemm 	struct stat sb;
118b8ba871bSPeter Wemm 	int fd;
119f0957ccaSPeter Wemm 	char *dp, *path;
120b8ba871bSPeter Wemm 
121b8ba871bSPeter Wemm 	/*
122b8ba871bSPeter Wemm 	 * !!!
123b8ba871bSPeter Wemm 	 * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
124b8ba871bSPeter Wemm 	 *
125b8ba871bSPeter Wemm 	 *
126b8ba871bSPeter Wemm 	 * If the recovery directory doesn't exist, try and create it.  As
127b8ba871bSPeter Wemm 	 * the recovery files are themselves protected from reading/writing
128b8ba871bSPeter Wemm 	 * by other than the owner, the worst that can happen is that a user
129b8ba871bSPeter Wemm 	 * would have permission to remove other user's recovery files.  If
130b8ba871bSPeter Wemm 	 * the sticky bit has the BSD semantics, that too will be impossible.
131b8ba871bSPeter Wemm 	 */
132b8ba871bSPeter Wemm 	if (opts_empty(sp, O_RECDIR, 0))
133b8ba871bSPeter Wemm 		goto err;
134b8ba871bSPeter Wemm 	dp = O_STR(sp, O_RECDIR);
135b8ba871bSPeter Wemm 	if (stat(dp, &sb)) {
136b8ba871bSPeter Wemm 		if (errno != ENOENT || mkdir(dp, 0)) {
137b8ba871bSPeter Wemm 			msgq(sp, M_SYSERR, "%s", dp);
138b8ba871bSPeter Wemm 			goto err;
139b8ba871bSPeter Wemm 		}
140b8ba871bSPeter Wemm 		(void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
141b8ba871bSPeter Wemm 	}
142b8ba871bSPeter Wemm 
143f0957ccaSPeter Wemm 	if ((path = join(dp, "vi.XXXXXX")) == NULL)
144f0957ccaSPeter Wemm 		goto err;
145f0957ccaSPeter Wemm 	if ((fd = rcv_mktemp(sp, path, dp)) == -1) {
146f0957ccaSPeter Wemm 		free(path);
147b8ba871bSPeter Wemm 		goto err;
148b8ba871bSPeter Wemm 	}
149f0957ccaSPeter Wemm 	(void)fchmod(fd, S_IRWXU);
150b8ba871bSPeter Wemm 	(void)close(fd);
151b8ba871bSPeter Wemm 
152f0957ccaSPeter Wemm 	ep->rcv_path = path;
153f0957ccaSPeter Wemm 	if (0) {
154b8ba871bSPeter Wemm err:		msgq(sp, M_ERR,
155b8ba871bSPeter Wemm 		    "056|Modifications not recoverable if the session fails");
156b8ba871bSPeter Wemm 		return (1);
157b8ba871bSPeter Wemm 	}
158b8ba871bSPeter Wemm 
159b8ba871bSPeter Wemm 	/* We believe the file is recoverable. */
160b8ba871bSPeter Wemm 	F_SET(ep, F_RCV_ON);
161b8ba871bSPeter Wemm 	return (0);
162b8ba871bSPeter Wemm }
163b8ba871bSPeter Wemm 
164b8ba871bSPeter Wemm /*
165b8ba871bSPeter Wemm  * rcv_init --
166b8ba871bSPeter Wemm  *	Force the file to be snapshotted for recovery.
167b8ba871bSPeter Wemm  *
168c271fa92SBaptiste Daroussin  * PUBLIC: int rcv_init(SCR *);
169b8ba871bSPeter Wemm  */
170b8ba871bSPeter Wemm int
rcv_init(SCR * sp)171f0957ccaSPeter Wemm rcv_init(SCR *sp)
172b8ba871bSPeter Wemm {
173b8ba871bSPeter Wemm 	EXF *ep;
174b8ba871bSPeter Wemm 	recno_t lno;
175b8ba871bSPeter Wemm 
176b8ba871bSPeter Wemm 	ep = sp->ep;
177b8ba871bSPeter Wemm 
178b8ba871bSPeter Wemm 	/* Only do this once. */
179b8ba871bSPeter Wemm 	F_CLR(ep, F_FIRSTMODIFY);
180b8ba871bSPeter Wemm 
181b8ba871bSPeter Wemm 	/* If we already know the file isn't recoverable, we're done. */
182b8ba871bSPeter Wemm 	if (!F_ISSET(ep, F_RCV_ON))
183b8ba871bSPeter Wemm 		return (0);
184b8ba871bSPeter Wemm 
185b8ba871bSPeter Wemm 	/* Turn off recoverability until we figure out if this will work. */
186b8ba871bSPeter Wemm 	F_CLR(ep, F_RCV_ON);
187b8ba871bSPeter Wemm 
188b8ba871bSPeter Wemm 	/* Test if we're recovering a file, not editing one. */
189b8ba871bSPeter Wemm 	if (ep->rcv_mpath == NULL) {
190b8ba871bSPeter Wemm 		/* Build a file to mail to the user. */
191b8ba871bSPeter Wemm 		if (rcv_mailfile(sp, 0, NULL))
192b8ba871bSPeter Wemm 			goto err;
193b8ba871bSPeter Wemm 
194b8ba871bSPeter Wemm 		/* Force a read of the entire file. */
195b8ba871bSPeter Wemm 		if (db_last(sp, &lno))
196b8ba871bSPeter Wemm 			goto err;
197b8ba871bSPeter Wemm 
198b8ba871bSPeter Wemm 		/* Turn on a busy message, and sync it to backing store. */
199b8ba871bSPeter Wemm 		sp->gp->scr_busy(sp,
200b8ba871bSPeter Wemm 		    "057|Copying file for recovery...", BUSY_ON);
201b8ba871bSPeter Wemm 		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
202b8ba871bSPeter Wemm 			msgq_str(sp, M_SYSERR, ep->rcv_path,
203b8ba871bSPeter Wemm 			    "058|Preservation failed: %s");
204b8ba871bSPeter Wemm 			sp->gp->scr_busy(sp, NULL, BUSY_OFF);
205b8ba871bSPeter Wemm 			goto err;
206b8ba871bSPeter Wemm 		}
207b8ba871bSPeter Wemm 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
208b8ba871bSPeter Wemm 	}
209b8ba871bSPeter Wemm 
210b8ba871bSPeter Wemm 	/* Turn off the owner execute bit. */
211b8ba871bSPeter Wemm 	(void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
212b8ba871bSPeter Wemm 
213b8ba871bSPeter Wemm 	/* We believe the file is recoverable. */
214b8ba871bSPeter Wemm 	F_SET(ep, F_RCV_ON);
215b8ba871bSPeter Wemm 	return (0);
216b8ba871bSPeter Wemm 
217b8ba871bSPeter Wemm err:	msgq(sp, M_ERR,
218b8ba871bSPeter Wemm 	    "059|Modifications not recoverable if the session fails");
219b8ba871bSPeter Wemm 	return (1);
220b8ba871bSPeter Wemm }
221b8ba871bSPeter Wemm 
222b8ba871bSPeter Wemm /*
223b8ba871bSPeter Wemm  * rcv_sync --
224b8ba871bSPeter Wemm  *	Sync the file, optionally:
225b8ba871bSPeter Wemm  *		flagging the backup file to be preserved
226b8ba871bSPeter Wemm  *		snapshotting the backup file and send email to the user
227b8ba871bSPeter Wemm  *		sending email to the user if the file was modified
228b8ba871bSPeter Wemm  *		ending the file session
229b8ba871bSPeter Wemm  *
230c271fa92SBaptiste Daroussin  * PUBLIC: int rcv_sync(SCR *, u_int);
231b8ba871bSPeter Wemm  */
232b8ba871bSPeter Wemm int
rcv_sync(SCR * sp,u_int flags)233110d525eSBaptiste Daroussin rcv_sync(SCR *sp, u_int flags)
234b8ba871bSPeter Wemm {
235b8ba871bSPeter Wemm 	EXF *ep;
236b8ba871bSPeter Wemm 	int fd, rval;
237f0957ccaSPeter Wemm 	char *dp, *buf;
238b8ba871bSPeter Wemm 
239b8ba871bSPeter Wemm 	/* Make sure that there's something to recover/sync. */
240b8ba871bSPeter Wemm 	ep = sp->ep;
241b8ba871bSPeter Wemm 	if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
242b8ba871bSPeter Wemm 		return (0);
243b8ba871bSPeter Wemm 
244b8ba871bSPeter Wemm 	/* Sync the file if it's been modified. */
245b8ba871bSPeter Wemm 	if (F_ISSET(ep, F_MODIFIED)) {
246b8ba871bSPeter Wemm 		if (ep->db->sync(ep->db, R_RECNOSYNC)) {
247b8ba871bSPeter Wemm 			F_CLR(ep, F_RCV_ON | F_RCV_NORM);
248b8ba871bSPeter Wemm 			msgq_str(sp, M_SYSERR,
249b8ba871bSPeter Wemm 			    ep->rcv_path, "060|File backup failed: %s");
250b8ba871bSPeter Wemm 			return (1);
251b8ba871bSPeter Wemm 		}
252b8ba871bSPeter Wemm 
253b8ba871bSPeter Wemm 		/* REQUEST: don't remove backing file on exit. */
254b8ba871bSPeter Wemm 		if (LF_ISSET(RCV_PRESERVE))
255b8ba871bSPeter Wemm 			F_SET(ep, F_RCV_NORM);
256b8ba871bSPeter Wemm 
257b8ba871bSPeter Wemm 		/* REQUEST: send email. */
258b8ba871bSPeter Wemm 		if (LF_ISSET(RCV_EMAIL))
259b8ba871bSPeter Wemm 			rcv_email(sp, ep->rcv_mpath);
260b8ba871bSPeter Wemm 	}
261b8ba871bSPeter Wemm 
262b8ba871bSPeter Wemm 	/*
263b8ba871bSPeter Wemm 	 * !!!
264b8ba871bSPeter Wemm 	 * Each time the user exec's :preserve, we have to snapshot all of
265b8ba871bSPeter Wemm 	 * the recovery information, i.e. it's like the user re-edited the
266b8ba871bSPeter Wemm 	 * file.  We copy the DB(3) backing file, and then create a new mail
267b8ba871bSPeter Wemm 	 * recovery file, it's simpler than exiting and reopening all of the
268b8ba871bSPeter Wemm 	 * underlying files.
269b8ba871bSPeter Wemm 	 *
270b8ba871bSPeter Wemm 	 * REQUEST: snapshot the file.
271b8ba871bSPeter Wemm 	 */
272b8ba871bSPeter Wemm 	rval = 0;
273b8ba871bSPeter Wemm 	if (LF_ISSET(RCV_SNAPSHOT)) {
274b8ba871bSPeter Wemm 		if (opts_empty(sp, O_RECDIR, 0))
275b8ba871bSPeter Wemm 			goto err;
276b8ba871bSPeter Wemm 		dp = O_STR(sp, O_RECDIR);
277f0957ccaSPeter Wemm 		if ((buf = join(dp, "vi.XXXXXX")) == NULL) {
278f0957ccaSPeter Wemm 			msgq(sp, M_SYSERR, NULL);
279b8ba871bSPeter Wemm 			goto err;
280f0957ccaSPeter Wemm 		}
281f0957ccaSPeter Wemm 		if ((fd = rcv_mktemp(sp, buf, dp)) == -1) {
282f0957ccaSPeter Wemm 			free(buf);
283f0957ccaSPeter Wemm 			goto err;
284f0957ccaSPeter Wemm 		}
285b8ba871bSPeter Wemm 		sp->gp->scr_busy(sp,
286b8ba871bSPeter Wemm 		    "061|Copying file for recovery...", BUSY_ON);
287b8ba871bSPeter Wemm 		if (rcv_copy(sp, fd, ep->rcv_path) ||
288b8ba871bSPeter Wemm 		    close(fd) || rcv_mailfile(sp, 1, buf)) {
289b8ba871bSPeter Wemm 			(void)unlink(buf);
290b8ba871bSPeter Wemm 			(void)close(fd);
291b8ba871bSPeter Wemm 			rval = 1;
292b8ba871bSPeter Wemm 		}
293f0957ccaSPeter Wemm 		free(buf);
294b8ba871bSPeter Wemm 		sp->gp->scr_busy(sp, NULL, BUSY_OFF);
295b8ba871bSPeter Wemm 	}
296b8ba871bSPeter Wemm 	if (0) {
297b8ba871bSPeter Wemm err:		rval = 1;
298b8ba871bSPeter Wemm 	}
299b8ba871bSPeter Wemm 
300b8ba871bSPeter Wemm 	/* REQUEST: end the file session. */
301b8ba871bSPeter Wemm 	if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
302b8ba871bSPeter Wemm 		rval = 1;
303b8ba871bSPeter Wemm 
304b8ba871bSPeter Wemm 	return (rval);
305b8ba871bSPeter Wemm }
306b8ba871bSPeter Wemm 
307b8ba871bSPeter Wemm /*
308b8ba871bSPeter Wemm  * rcv_mailfile --
309b8ba871bSPeter Wemm  *	Build the file to mail to the user.
310b8ba871bSPeter Wemm  */
311b8ba871bSPeter Wemm static int
rcv_mailfile(SCR * sp,int issync,char * cp_path)312110d525eSBaptiste Daroussin rcv_mailfile(SCR *sp, int issync, char *cp_path)
313b8ba871bSPeter Wemm {
314b8ba871bSPeter Wemm 	EXF *ep;
315b8ba871bSPeter Wemm 	GS *gp;
316b8ba871bSPeter Wemm 	struct passwd *pw;
317f0957ccaSPeter Wemm 	int len;
318b8ba871bSPeter Wemm 	time_t now;
319b8ba871bSPeter Wemm 	uid_t uid;
320b8ba871bSPeter Wemm 	int fd;
321f0957ccaSPeter Wemm 	FILE *fp;
322f0957ccaSPeter Wemm 	char *dp, *p, *t, *qt, *buf, *mpath;
323b8ba871bSPeter Wemm 	char *t1, *t2, *t3;
324f0957ccaSPeter Wemm 	int st;
325b8ba871bSPeter Wemm 
326b8ba871bSPeter Wemm 	/*
327b8ba871bSPeter Wemm 	 * XXX
328f0957ccaSPeter Wemm 	 * MAXHOSTNAMELEN/HOST_NAME_MAX are deprecated. We try sysconf(3)
329f0957ccaSPeter Wemm 	 * first, then fallback to _POSIX_HOST_NAME_MAX.
330b8ba871bSPeter Wemm 	 */
331f0957ccaSPeter Wemm 	char *host;
332f0957ccaSPeter Wemm 	long hostmax = sysconf(_SC_HOST_NAME_MAX);
333f0957ccaSPeter Wemm 	if (hostmax < 0)
334f0957ccaSPeter Wemm 		hostmax = _POSIX_HOST_NAME_MAX;
335b8ba871bSPeter Wemm 
336b8ba871bSPeter Wemm 	gp = sp->gp;
337b8ba871bSPeter Wemm 	if ((pw = getpwuid(uid = getuid())) == NULL) {
338b8ba871bSPeter Wemm 		msgq(sp, M_ERR,
339b8ba871bSPeter Wemm 		    "062|Information on user id %u not found", uid);
340b8ba871bSPeter Wemm 		return (1);
341b8ba871bSPeter Wemm 	}
342b8ba871bSPeter Wemm 
343b8ba871bSPeter Wemm 	if (opts_empty(sp, O_RECDIR, 0))
344b8ba871bSPeter Wemm 		return (1);
345b8ba871bSPeter Wemm 	dp = O_STR(sp, O_RECDIR);
346f0957ccaSPeter Wemm 	if ((mpath = join(dp, "recover.XXXXXX")) == NULL) {
347f0957ccaSPeter Wemm 		msgq(sp, M_SYSERR, NULL);
348b8ba871bSPeter Wemm 		return (1);
349f0957ccaSPeter Wemm 	}
350f0957ccaSPeter Wemm 	if ((fd = rcv_mktemp(sp, mpath, dp)) == -1) {
351f0957ccaSPeter Wemm 		free(mpath);
352f0957ccaSPeter Wemm 		return (1);
353f0957ccaSPeter Wemm 	}
354f0957ccaSPeter Wemm 	if ((fp = fdopen(fd, "w")) == NULL) {
355f0957ccaSPeter Wemm 		free(mpath);
356f0957ccaSPeter Wemm 		close(fd);
357f0957ccaSPeter Wemm 		return (1);
358f0957ccaSPeter Wemm 	}
359b8ba871bSPeter Wemm 
360b8ba871bSPeter Wemm 	/*
361b8ba871bSPeter Wemm 	 * XXX
362b8ba871bSPeter Wemm 	 * We keep an open lock on the file so that the recover option can
363b8ba871bSPeter Wemm 	 * distinguish between files that are live and those that need to
364b8ba871bSPeter Wemm 	 * be recovered.  There's an obvious window between the mkstemp call
365b8ba871bSPeter Wemm 	 * and the lock, but it's pretty small.
366b8ba871bSPeter Wemm 	 */
367b8ba871bSPeter Wemm 	ep = sp->ep;
368f0957ccaSPeter Wemm 	if (file_lock(sp, NULL, fd, 1) != LOCK_SUCCESS)
369b8ba871bSPeter Wemm 		msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
370b8ba871bSPeter Wemm 	if (!issync) {
371b8ba871bSPeter Wemm 		/* Save the recover file descriptor, and mail path. */
372f0957ccaSPeter Wemm 		ep->rcv_fd = dup(fd);
373f0957ccaSPeter Wemm 		ep->rcv_mpath = mpath;
374b8ba871bSPeter Wemm 		cp_path = ep->rcv_path;
375b8ba871bSPeter Wemm 	}
376b8ba871bSPeter Wemm 
377b8ba871bSPeter Wemm 	t = sp->frp->name;
378b8ba871bSPeter Wemm 	if ((p = strrchr(t, '/')) == NULL)
379b8ba871bSPeter Wemm 		p = t;
380b8ba871bSPeter Wemm 	else
381b8ba871bSPeter Wemm 		++p;
382b8ba871bSPeter Wemm 	(void)time(&now);
383f0957ccaSPeter Wemm 
384f0957ccaSPeter Wemm 	if ((st = rcv_dlnwrite(sp, "file", t, fp))) {
385f0957ccaSPeter Wemm 		if (st == 1)
386f0957ccaSPeter Wemm 			goto werr;
387f0957ccaSPeter Wemm 		goto err;
388f0957ccaSPeter Wemm 	}
389f0957ccaSPeter Wemm 	if ((st = rcv_dlnwrite(sp, "path", cp_path, fp))) {
390f0957ccaSPeter Wemm 		if (st == 1)
391f0957ccaSPeter Wemm 			goto werr;
392f0957ccaSPeter Wemm 		goto err;
393f0957ccaSPeter Wemm 	}
394f0957ccaSPeter Wemm 
395110d525eSBaptiste Daroussin 	MALLOC(sp, host, hostmax + 1);
396f0957ccaSPeter Wemm 	if (host == NULL)
397f0957ccaSPeter Wemm 		goto err;
398f0957ccaSPeter Wemm 	(void)gethostname(host, hostmax + 1);
399f0957ccaSPeter Wemm 
400f0957ccaSPeter Wemm 	len = fprintf(fp, "%s%s%s\n%s%s%s%s\n%s%.40s\n%s\n\n",
401f0957ccaSPeter Wemm 	    "From: root@", host, " (Nvi recovery program)",
402f0957ccaSPeter Wemm 	    "To: ", pw->pw_name, "@", host,
403b8ba871bSPeter Wemm 	    "Subject: Nvi saved the file ", p,
404b8ba871bSPeter Wemm 	    "Precedence: bulk");		/* For vacation(1). */
405f0957ccaSPeter Wemm 	if (len < 0) {
406f0957ccaSPeter Wemm 		free(host);
407b8ba871bSPeter Wemm 		goto werr;
408f0957ccaSPeter Wemm 	}
409b8ba871bSPeter Wemm 
410f0957ccaSPeter Wemm 	if ((qt = quote(t)) == NULL) {
411f0957ccaSPeter Wemm 		free(host);
412f0957ccaSPeter Wemm 		msgq(sp, M_SYSERR, NULL);
413f0957ccaSPeter Wemm 		goto err;
414f0957ccaSPeter Wemm 	}
415f0957ccaSPeter Wemm 	len = asprintf(&buf, "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
416b8ba871bSPeter Wemm 	    "On ", ctime(&now), ", the user ", pw->pw_name,
417b8ba871bSPeter Wemm 	    " was editing a file named ", t, " on the machine ",
418b8ba871bSPeter Wemm 	    host, ", when it was saved for recovery. ",
419b8ba871bSPeter Wemm 	    "You can recover most, if not all, of the changes ",
420110d525eSBaptiste Daroussin 	    "to this file using the -r option to ", getprogname(), ":\n\n\t",
421110d525eSBaptiste Daroussin 	    getprogname(), " -r ", qt);
422f0957ccaSPeter Wemm 	free(qt);
423f0957ccaSPeter Wemm 	free(host);
424755cc40cSBaptiste Daroussin 	if (len == -1) {
425f0957ccaSPeter Wemm 		msgq(sp, M_SYSERR, NULL);
426b8ba871bSPeter Wemm 		goto err;
427b8ba871bSPeter Wemm 	}
428b8ba871bSPeter Wemm 
429b8ba871bSPeter Wemm 	/*
430b8ba871bSPeter Wemm 	 * Format the message.  (Yes, I know it's silly.)
431b8ba871bSPeter Wemm 	 * Requires that the message end in a <newline>.
432b8ba871bSPeter Wemm 	 */
433b8ba871bSPeter Wemm #define	FMTCOLS	60
434b8ba871bSPeter Wemm 	for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
435b8ba871bSPeter Wemm 		/* Check for a short length. */
436b8ba871bSPeter Wemm 		if (len <= FMTCOLS) {
437b8ba871bSPeter Wemm 			t2 = t1 + (len - 1);
438b8ba871bSPeter Wemm 			goto wout;
439b8ba871bSPeter Wemm 		}
440b8ba871bSPeter Wemm 
441b8ba871bSPeter Wemm 		/* Check for a required <newline>. */
442b8ba871bSPeter Wemm 		t2 = strchr(t1, '\n');
443b8ba871bSPeter Wemm 		if (t2 - t1 <= FMTCOLS)
444b8ba871bSPeter Wemm 			goto wout;
445b8ba871bSPeter Wemm 
446b8ba871bSPeter Wemm 		/* Find the closest space, if any. */
447b8ba871bSPeter Wemm 		for (t3 = t2; t2 > t1; --t2)
448b8ba871bSPeter Wemm 			if (*t2 == ' ') {
449b8ba871bSPeter Wemm 				if (t2 - t1 <= FMTCOLS)
450b8ba871bSPeter Wemm 					goto wout;
451b8ba871bSPeter Wemm 				t3 = t2;
452b8ba871bSPeter Wemm 			}
453b8ba871bSPeter Wemm 		t2 = t3;
454b8ba871bSPeter Wemm 
455b8ba871bSPeter Wemm 		/* t2 points to the last character to display. */
456b8ba871bSPeter Wemm wout:		*t2++ = '\n';
457b8ba871bSPeter Wemm 
458b8ba871bSPeter Wemm 		/* t2 points one after the last character to display. */
459f0957ccaSPeter Wemm 		if (fwrite(t1, 1, t2 - t1, fp) != t2 - t1) {
460f0957ccaSPeter Wemm 			free(buf);
461b8ba871bSPeter Wemm 			goto werr;
462b8ba871bSPeter Wemm 		}
463f0957ccaSPeter Wemm 	}
464b8ba871bSPeter Wemm 
465b8ba871bSPeter Wemm 	if (issync) {
466f0957ccaSPeter Wemm 		fflush(fp);
467b8ba871bSPeter Wemm 		rcv_email(sp, mpath);
468f0957ccaSPeter Wemm 		free(mpath);
469f0957ccaSPeter Wemm 	}
470f0957ccaSPeter Wemm 	if (fclose(fp)) {
471f0957ccaSPeter Wemm 		free(buf);
472b8ba871bSPeter Wemm werr:		msgq(sp, M_SYSERR, "065|Recovery file");
473b8ba871bSPeter Wemm 		goto err;
474b8ba871bSPeter Wemm 	}
475f0957ccaSPeter Wemm 	free(buf);
476b8ba871bSPeter Wemm 	return (0);
477b8ba871bSPeter Wemm 
478b8ba871bSPeter Wemm err:	if (!issync)
479b8ba871bSPeter Wemm 		ep->rcv_fd = -1;
480f0957ccaSPeter Wemm 	if (fp != NULL)
481f0957ccaSPeter Wemm 		(void)fclose(fp);
482b8ba871bSPeter Wemm 	return (1);
483b8ba871bSPeter Wemm }
484b8ba871bSPeter Wemm 
485b8ba871bSPeter Wemm /*
486b8ba871bSPeter Wemm  *	people making love
487b8ba871bSPeter Wemm  *	never exactly the same
488b8ba871bSPeter Wemm  *	just like a snowflake
489b8ba871bSPeter Wemm  *
490b8ba871bSPeter Wemm  * rcv_list --
491b8ba871bSPeter Wemm  *	List the files that can be recovered by this user.
492b8ba871bSPeter Wemm  *
493c271fa92SBaptiste Daroussin  * PUBLIC: int rcv_list(SCR *);
494b8ba871bSPeter Wemm  */
495b8ba871bSPeter Wemm int
rcv_list(SCR * sp)496f0957ccaSPeter Wemm rcv_list(SCR *sp)
497b8ba871bSPeter Wemm {
498b8ba871bSPeter Wemm 	struct dirent *dp;
499b8ba871bSPeter Wemm 	struct stat sb;
500b8ba871bSPeter Wemm 	DIR *dirp;
501b8ba871bSPeter Wemm 	FILE *fp;
502b8ba871bSPeter Wemm 	int found;
503f0957ccaSPeter Wemm 	char *p, *file, *path;
504f0957ccaSPeter Wemm 	char *dtype, *data;
505f0957ccaSPeter Wemm 	int st;
506b8ba871bSPeter Wemm 
507b8ba871bSPeter Wemm 	/* Open the recovery directory for reading. */
508b8ba871bSPeter Wemm 	if (opts_empty(sp, O_RECDIR, 0))
509b8ba871bSPeter Wemm 		return (1);
510b8ba871bSPeter Wemm 	p = O_STR(sp, O_RECDIR);
511b8ba871bSPeter Wemm 	if (chdir(p) || (dirp = opendir(".")) == NULL) {
512b8ba871bSPeter Wemm 		msgq_str(sp, M_SYSERR, p, "recdir: %s");
513b8ba871bSPeter Wemm 		return (1);
514b8ba871bSPeter Wemm 	}
515b8ba871bSPeter Wemm 
516b8ba871bSPeter Wemm 	/* Read the directory. */
517b8ba871bSPeter Wemm 	for (found = 0; (dp = readdir(dirp)) != NULL;) {
518b8ba871bSPeter Wemm 		if (strncmp(dp->d_name, "recover.", 8))
519b8ba871bSPeter Wemm 			continue;
520b8ba871bSPeter Wemm 
521f0957ccaSPeter Wemm 		/* If it's readable, it's recoverable. */
522f0957ccaSPeter Wemm 		if ((fp = fopen(dp->d_name, "r")) == NULL)
523b8ba871bSPeter Wemm 			continue;
524b8ba871bSPeter Wemm 
525f0957ccaSPeter Wemm 		switch (file_lock(sp, NULL, fileno(fp), 1)) {
526b8ba871bSPeter Wemm 		case LOCK_FAILED:
527b8ba871bSPeter Wemm 			/*
528b8ba871bSPeter Wemm 			 * XXX
529b8ba871bSPeter Wemm 			 * Assume that a lock can't be acquired, but that we
530b8ba871bSPeter Wemm 			 * should permit recovery anyway.  If this is wrong,
531b8ba871bSPeter Wemm 			 * and someone else is using the file, we're going to
532b8ba871bSPeter Wemm 			 * die horribly.
533b8ba871bSPeter Wemm 			 */
534b8ba871bSPeter Wemm 			break;
535b8ba871bSPeter Wemm 		case LOCK_SUCCESS:
536b8ba871bSPeter Wemm 			break;
537b8ba871bSPeter Wemm 		case LOCK_UNAVAIL:
538b8ba871bSPeter Wemm 			/* If it's locked, it's live. */
539b8ba871bSPeter Wemm 			(void)fclose(fp);
540b8ba871bSPeter Wemm 			continue;
541b8ba871bSPeter Wemm 		}
542b8ba871bSPeter Wemm 
543b8ba871bSPeter Wemm 		/* Check the headers. */
544f0957ccaSPeter Wemm 		for (file = NULL, path = NULL;
545f0957ccaSPeter Wemm 		    file == NULL || path == NULL;) {
546f0957ccaSPeter Wemm 			if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
547f0957ccaSPeter Wemm 				if (st == 1)
548b8ba871bSPeter Wemm 					msgq_str(sp, M_ERR, dp->d_name,
549b8ba871bSPeter Wemm 					    "066|%s: malformed recovery file");
550b8ba871bSPeter Wemm 				goto next;
551b8ba871bSPeter Wemm 			}
552f0957ccaSPeter Wemm 			if (dtype == NULL)
553f0957ccaSPeter Wemm 				continue;
554f0957ccaSPeter Wemm 			if (!strcmp(dtype, "file"))
555f0957ccaSPeter Wemm 				file = data;
556f0957ccaSPeter Wemm 			else if (!strcmp(dtype, "path"))
557f0957ccaSPeter Wemm 				path = data;
558f0957ccaSPeter Wemm 			else
559f0957ccaSPeter Wemm 				free(data);
560f0957ccaSPeter Wemm 		}
561b8ba871bSPeter Wemm 
562b8ba871bSPeter Wemm 		/*
563b8ba871bSPeter Wemm 		 * If the file doesn't exist, it's an orphaned recovery file,
564b8ba871bSPeter Wemm 		 * toss it.
565b8ba871bSPeter Wemm 		 *
566b8ba871bSPeter Wemm 		 * XXX
567b8ba871bSPeter Wemm 		 * This can occur if the backup file was deleted and we crashed
568b8ba871bSPeter Wemm 		 * before deleting the email file.
569b8ba871bSPeter Wemm 		 */
570b8ba871bSPeter Wemm 		errno = 0;
571f0957ccaSPeter Wemm 		if (stat(path, &sb) &&
572b8ba871bSPeter Wemm 		    errno == ENOENT) {
573b8ba871bSPeter Wemm 			(void)unlink(dp->d_name);
574b8ba871bSPeter Wemm 			goto next;
575b8ba871bSPeter Wemm 		}
576b8ba871bSPeter Wemm 
577b8ba871bSPeter Wemm 		/* Get the last modification time and display. */
578b8ba871bSPeter Wemm 		(void)fstat(fileno(fp), &sb);
579b8ba871bSPeter Wemm 		(void)printf("%.24s: %s\n",
580f0957ccaSPeter Wemm 		    ctime(&sb.st_mtime), file);
581b8ba871bSPeter Wemm 		found = 1;
582b8ba871bSPeter Wemm 
583b8ba871bSPeter Wemm 		/* Close, discarding lock. */
584b8ba871bSPeter Wemm next:		(void)fclose(fp);
585f0957ccaSPeter Wemm 		free(file);
586f0957ccaSPeter Wemm 		free(path);
587b8ba871bSPeter Wemm 	}
588b8ba871bSPeter Wemm 	if (found == 0)
589110d525eSBaptiste Daroussin 		(void)printf("%s: No files to recover\n", getprogname());
590b8ba871bSPeter Wemm 	(void)closedir(dirp);
591b8ba871bSPeter Wemm 	return (0);
592b8ba871bSPeter Wemm }
593b8ba871bSPeter Wemm 
594b8ba871bSPeter Wemm /*
595b8ba871bSPeter Wemm  * rcv_read --
596b8ba871bSPeter Wemm  *	Start a recovered file as the file to edit.
597b8ba871bSPeter Wemm  *
598c271fa92SBaptiste Daroussin  * PUBLIC: int rcv_read(SCR *, FREF *);
599b8ba871bSPeter Wemm  */
600b8ba871bSPeter Wemm int
rcv_read(SCR * sp,FREF * frp)601110d525eSBaptiste Daroussin rcv_read(SCR *sp, FREF *frp)
602b8ba871bSPeter Wemm {
603b8ba871bSPeter Wemm 	struct dirent *dp;
604b8ba871bSPeter Wemm 	struct stat sb;
605b8ba871bSPeter Wemm 	DIR *dirp;
606f0957ccaSPeter Wemm 	FILE *fp;
607b8ba871bSPeter Wemm 	EXF *ep;
608f0957ccaSPeter Wemm 	struct timespec rec_mtim = { 0, 0 };
609f0957ccaSPeter Wemm 	int found, locked = 0, requested, sv_fd;
610b8ba871bSPeter Wemm 	char *name, *p, *t, *rp, *recp, *pathp;
611f0957ccaSPeter Wemm 	char *file, *path, *recpath;
612f0957ccaSPeter Wemm 	char *dtype, *data;
613f0957ccaSPeter Wemm 	int st;
614b8ba871bSPeter Wemm 
615b8ba871bSPeter Wemm 	if (opts_empty(sp, O_RECDIR, 0))
616b8ba871bSPeter Wemm 		return (1);
617b8ba871bSPeter Wemm 	rp = O_STR(sp, O_RECDIR);
618b8ba871bSPeter Wemm 	if ((dirp = opendir(rp)) == NULL) {
619c271fa92SBaptiste Daroussin 		msgq_str(sp, M_SYSERR, rp, "%s");
620b8ba871bSPeter Wemm 		return (1);
621b8ba871bSPeter Wemm 	}
622b8ba871bSPeter Wemm 
623b8ba871bSPeter Wemm 	name = frp->name;
624b8ba871bSPeter Wemm 	sv_fd = -1;
625b8ba871bSPeter Wemm 	recp = pathp = NULL;
626b8ba871bSPeter Wemm 	for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
627b8ba871bSPeter Wemm 		if (strncmp(dp->d_name, "recover.", 8))
628b8ba871bSPeter Wemm 			continue;
629f0957ccaSPeter Wemm 		if ((recpath = join(rp, dp->d_name)) == NULL) {
630f0957ccaSPeter Wemm 			msgq(sp, M_SYSERR, NULL);
631b8ba871bSPeter Wemm 			continue;
632f0957ccaSPeter Wemm 		}
633b8ba871bSPeter Wemm 
634f0957ccaSPeter Wemm 		/* If it's readable, it's recoverable. */
635f0957ccaSPeter Wemm 		if ((fp = fopen(recpath, "r")) == NULL) {
636f0957ccaSPeter Wemm 			free(recpath);
637f0957ccaSPeter Wemm 			continue;
638f0957ccaSPeter Wemm 		}
639f0957ccaSPeter Wemm 
640f0957ccaSPeter Wemm 		switch (file_lock(sp, NULL, fileno(fp), 1)) {
641b8ba871bSPeter Wemm 		case LOCK_FAILED:
642b8ba871bSPeter Wemm 			/*
643b8ba871bSPeter Wemm 			 * XXX
644b8ba871bSPeter Wemm 			 * Assume that a lock can't be acquired, but that we
645b8ba871bSPeter Wemm 			 * should permit recovery anyway.  If this is wrong,
646b8ba871bSPeter Wemm 			 * and someone else is using the file, we're going to
647b8ba871bSPeter Wemm 			 * die horribly.
648b8ba871bSPeter Wemm 			 */
649b8ba871bSPeter Wemm 			locked = 0;
650b8ba871bSPeter Wemm 			break;
651b8ba871bSPeter Wemm 		case LOCK_SUCCESS:
652b8ba871bSPeter Wemm 			locked = 1;
653b8ba871bSPeter Wemm 			break;
654b8ba871bSPeter Wemm 		case LOCK_UNAVAIL:
655b8ba871bSPeter Wemm 			/* If it's locked, it's live. */
656f0957ccaSPeter Wemm 			(void)fclose(fp);
657b8ba871bSPeter Wemm 			continue;
658b8ba871bSPeter Wemm 		}
659b8ba871bSPeter Wemm 
660b8ba871bSPeter Wemm 		/* Check the headers. */
661f0957ccaSPeter Wemm 		for (file = NULL, path = NULL;
662f0957ccaSPeter Wemm 		    file == NULL || path == NULL;) {
663f0957ccaSPeter Wemm 			if ((st = rcv_dlnread(sp, &dtype, &data, fp))) {
664f0957ccaSPeter Wemm 				if (st == 1)
665f0957ccaSPeter Wemm 					msgq_str(sp, M_ERR, dp->d_name,
666b8ba871bSPeter Wemm 					    "067|%s: malformed recovery file");
667b8ba871bSPeter Wemm 				goto next;
668b8ba871bSPeter Wemm 			}
669f0957ccaSPeter Wemm 			if (dtype == NULL)
670f0957ccaSPeter Wemm 				continue;
671f0957ccaSPeter Wemm 			if (!strcmp(dtype, "file"))
672f0957ccaSPeter Wemm 				file = data;
673f0957ccaSPeter Wemm 			else if (!strcmp(dtype, "path"))
674f0957ccaSPeter Wemm 				path = data;
675f0957ccaSPeter Wemm 			else
676f0957ccaSPeter Wemm 				free(data);
677f0957ccaSPeter Wemm 		}
678b8ba871bSPeter Wemm 		++found;
679b8ba871bSPeter Wemm 
680b8ba871bSPeter Wemm 		/*
681b8ba871bSPeter Wemm 		 * If the file doesn't exist, it's an orphaned recovery file,
682b8ba871bSPeter Wemm 		 * toss it.
683b8ba871bSPeter Wemm 		 *
684b8ba871bSPeter Wemm 		 * XXX
685b8ba871bSPeter Wemm 		 * This can occur if the backup file was deleted and we crashed
686b8ba871bSPeter Wemm 		 * before deleting the email file.
687b8ba871bSPeter Wemm 		 */
688b8ba871bSPeter Wemm 		errno = 0;
689f0957ccaSPeter Wemm 		if (stat(path, &sb) &&
690b8ba871bSPeter Wemm 		    errno == ENOENT) {
691b8ba871bSPeter Wemm 			(void)unlink(dp->d_name);
692b8ba871bSPeter Wemm 			goto next;
693b8ba871bSPeter Wemm 		}
694b8ba871bSPeter Wemm 
695b8ba871bSPeter Wemm 		/* Check the file name. */
696f0957ccaSPeter Wemm 		if (strcmp(file, name))
697b8ba871bSPeter Wemm 			goto next;
698b8ba871bSPeter Wemm 
699b8ba871bSPeter Wemm 		++requested;
700b8ba871bSPeter Wemm 
701f0957ccaSPeter Wemm 		/* If we've found more than one, take the most recent. */
702f0957ccaSPeter Wemm 		(void)fstat(fileno(fp), &sb);
703f0957ccaSPeter Wemm 		if (recp == NULL ||
704*6680e5a5SBaptiste Daroussin #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC
705*6680e5a5SBaptiste Daroussin 		    timespeccmp(&rec_mtim, &sb.st_mtimespec, <)) {
706*6680e5a5SBaptiste Daroussin #elif defined HAVE_STRUCT_STAT_ST_MTIM
707755cc40cSBaptiste Daroussin 		    timespeccmp(&rec_mtim, &sb.st_mtim, <)) {
708*6680e5a5SBaptiste Daroussin #else
709*6680e5a5SBaptiste Daroussin 		    rec_mtim.tv_sec < sb.st_mtime) {
710*6680e5a5SBaptiste Daroussin #endif
711b8ba871bSPeter Wemm 			p = recp;
712b8ba871bSPeter Wemm 			t = pathp;
713f0957ccaSPeter Wemm 			recp = recpath;
714f0957ccaSPeter Wemm 			pathp = path;
715b8ba871bSPeter Wemm 			if (p != NULL) {
716b8ba871bSPeter Wemm 				free(p);
717b8ba871bSPeter Wemm 				free(t);
718b8ba871bSPeter Wemm 			}
719*6680e5a5SBaptiste Daroussin #if defined HAVE_STRUCT_STAT_ST_MTIMESPEC
720*6680e5a5SBaptiste Daroussin 			rec_mtim = sb.st_mtimespec;
721*6680e5a5SBaptiste Daroussin #elif defined HAVE_STRUCT_STAT_ST_MTIM
722755cc40cSBaptiste Daroussin 			rec_mtim = sb.st_mtim;
723*6680e5a5SBaptiste Daroussin #else
724*6680e5a5SBaptiste Daroussin 			rec_mtim.tv_sec = sb.st_mtime;
725*6680e5a5SBaptiste Daroussin #endif
726b8ba871bSPeter Wemm 			if (sv_fd != -1)
727b8ba871bSPeter Wemm 				(void)close(sv_fd);
728f0957ccaSPeter Wemm 			sv_fd = dup(fileno(fp));
729f0957ccaSPeter Wemm 		} else {
730f0957ccaSPeter Wemm next:			free(recpath);
731f0957ccaSPeter Wemm 			free(path);
732f0957ccaSPeter Wemm 		}
733f0957ccaSPeter Wemm 		(void)fclose(fp);
734f0957ccaSPeter Wemm 		free(file);
735b8ba871bSPeter Wemm 	}
736b8ba871bSPeter Wemm 	(void)closedir(dirp);
737b8ba871bSPeter Wemm 
738b8ba871bSPeter Wemm 	if (recp == NULL) {
739b8ba871bSPeter Wemm 		msgq_str(sp, M_INFO, name,
740b8ba871bSPeter Wemm 		    "068|No files named %s, readable by you, to recover");
741b8ba871bSPeter Wemm 		return (1);
742b8ba871bSPeter Wemm 	}
743b8ba871bSPeter Wemm 	if (found) {
744b8ba871bSPeter Wemm 		if (requested > 1)
745b8ba871bSPeter Wemm 			msgq(sp, M_INFO,
746b8ba871bSPeter Wemm 	    "069|There are older versions of this file for you to recover");
747b8ba871bSPeter Wemm 		if (found > requested)
748b8ba871bSPeter Wemm 			msgq(sp, M_INFO,
749b8ba871bSPeter Wemm 			    "070|There are other files for you to recover");
750b8ba871bSPeter Wemm 	}
751b8ba871bSPeter Wemm 
752b8ba871bSPeter Wemm 	/*
753b8ba871bSPeter Wemm 	 * Create the FREF structure, start the btree file.
754b8ba871bSPeter Wemm 	 *
755b8ba871bSPeter Wemm 	 * XXX
756b8ba871bSPeter Wemm 	 * file_init() is going to set ep->rcv_path.
757b8ba871bSPeter Wemm 	 */
758f0957ccaSPeter Wemm 	if (file_init(sp, frp, pathp, 0)) {
759b8ba871bSPeter Wemm 		free(recp);
760b8ba871bSPeter Wemm 		free(pathp);
761b8ba871bSPeter Wemm 		(void)close(sv_fd);
762b8ba871bSPeter Wemm 		return (1);
763b8ba871bSPeter Wemm 	}
764f0957ccaSPeter Wemm 	free(pathp);
765b8ba871bSPeter Wemm 
766b8ba871bSPeter Wemm 	/*
767b8ba871bSPeter Wemm 	 * We keep an open lock on the file so that the recover option can
768b8ba871bSPeter Wemm 	 * distinguish between files that are live and those that need to
769b8ba871bSPeter Wemm 	 * be recovered.  The lock is already acquired, just copy it.
770b8ba871bSPeter Wemm 	 */
771b8ba871bSPeter Wemm 	ep = sp->ep;
772b8ba871bSPeter Wemm 	ep->rcv_mpath = recp;
773b8ba871bSPeter Wemm 	ep->rcv_fd = sv_fd;
774b8ba871bSPeter Wemm 	if (!locked)
775b8ba871bSPeter Wemm 		F_SET(frp, FR_UNLOCKED);
776b8ba871bSPeter Wemm 
777b8ba871bSPeter Wemm 	/* We believe the file is recoverable. */
778b8ba871bSPeter Wemm 	F_SET(ep, F_RCV_ON);
779b8ba871bSPeter Wemm 	return (0);
780b8ba871bSPeter Wemm }
781b8ba871bSPeter Wemm 
782b8ba871bSPeter Wemm /*
783b8ba871bSPeter Wemm  * rcv_copy --
784b8ba871bSPeter Wemm  *	Copy a recovery file.
785b8ba871bSPeter Wemm  */
786b8ba871bSPeter Wemm static int
787110d525eSBaptiste Daroussin rcv_copy(SCR *sp, int wfd, char *fname)
788b8ba871bSPeter Wemm {
789b8ba871bSPeter Wemm 	int nr, nw, off, rfd;
790b8ba871bSPeter Wemm 	char buf[8 * 1024];
791b8ba871bSPeter Wemm 
792b8ba871bSPeter Wemm 	if ((rfd = open(fname, O_RDONLY, 0)) == -1)
793b8ba871bSPeter Wemm 		goto err;
794b8ba871bSPeter Wemm 	while ((nr = read(rfd, buf, sizeof(buf))) > 0)
795b8ba871bSPeter Wemm 		for (off = 0; nr; nr -= nw, off += nw)
796b8ba871bSPeter Wemm 			if ((nw = write(wfd, buf + off, nr)) < 0)
797b8ba871bSPeter Wemm 				goto err;
798b8ba871bSPeter Wemm 	if (nr == 0)
799b8ba871bSPeter Wemm 		return (0);
800b8ba871bSPeter Wemm 
801b8ba871bSPeter Wemm err:	msgq_str(sp, M_SYSERR, fname, "%s");
802b8ba871bSPeter Wemm 	return (1);
803b8ba871bSPeter Wemm }
804b8ba871bSPeter Wemm 
805b8ba871bSPeter Wemm /*
806b8ba871bSPeter Wemm  * rcv_mktemp --
807b8ba871bSPeter Wemm  *	Paranoid make temporary file routine.
808b8ba871bSPeter Wemm  */
809b8ba871bSPeter Wemm static int
810110d525eSBaptiste Daroussin rcv_mktemp(SCR *sp, char *path, char *dname)
811b8ba871bSPeter Wemm {
812b8ba871bSPeter Wemm 	int fd;
813b8ba871bSPeter Wemm 
814b8ba871bSPeter Wemm 	if ((fd = mkstemp(path)) == -1)
815b8ba871bSPeter Wemm 		msgq_str(sp, M_SYSERR, dname, "%s");
816b8ba871bSPeter Wemm 	return (fd);
817b8ba871bSPeter Wemm }
818b8ba871bSPeter Wemm 
819b8ba871bSPeter Wemm /*
820b8ba871bSPeter Wemm  * rcv_email --
821b8ba871bSPeter Wemm  *	Send email.
822b8ba871bSPeter Wemm  */
823b8ba871bSPeter Wemm static void
824110d525eSBaptiste Daroussin rcv_email(SCR *sp, char *fname)
825b8ba871bSPeter Wemm {
826f0957ccaSPeter Wemm 	char *buf;
827b8ba871bSPeter Wemm 
828755cc40cSBaptiste Daroussin 	if (asprintf(&buf, _PATH_SENDMAIL " -odb -t < %s", fname) == -1) {
829f0957ccaSPeter Wemm 		msgq_str(sp, M_ERR, strerror(errno),
830f0957ccaSPeter Wemm 		    "071|not sending email: %s");
831f0957ccaSPeter Wemm 		return;
832b8ba871bSPeter Wemm 	}
833f0957ccaSPeter Wemm 	(void)system(buf);
834f0957ccaSPeter Wemm 	free(buf);
835f0957ccaSPeter Wemm }
836f0957ccaSPeter Wemm 
837f0957ccaSPeter Wemm /*
838f0957ccaSPeter Wemm  * rcv_dlnwrite --
839f0957ccaSPeter Wemm  *	Encode a string into an X-vi-data line and write it.
840f0957ccaSPeter Wemm  */
841f0957ccaSPeter Wemm static int
842110d525eSBaptiste Daroussin rcv_dlnwrite(SCR *sp, const char *dtype, const char *src, FILE *fp)
843f0957ccaSPeter Wemm {
844f0957ccaSPeter Wemm 	char *bp = NULL, *p;
845f0957ccaSPeter Wemm 	size_t blen = 0;
846f0957ccaSPeter Wemm 	size_t dlen, len;
847f0957ccaSPeter Wemm 	int plen, xlen;
848f0957ccaSPeter Wemm 
849f0957ccaSPeter Wemm 	len = strlen(src);
850f0957ccaSPeter Wemm 	dlen = strlen(dtype);
851f0957ccaSPeter Wemm 	GET_SPACE_GOTOC(sp, bp, blen, (len + 2) / 3 * 4 + dlen + 2);
852f0957ccaSPeter Wemm 	(void)memcpy(bp, dtype, dlen);
853f0957ccaSPeter Wemm 	bp[dlen] = ';';
854f0957ccaSPeter Wemm 	if ((xlen = b64_ntop((u_char *)src,
855f0957ccaSPeter Wemm 	    len, bp + dlen + 1, blen)) == -1)
856f0957ccaSPeter Wemm 		goto err;
857f0957ccaSPeter Wemm 	xlen += dlen + 1;
858f0957ccaSPeter Wemm 
859f0957ccaSPeter Wemm 	/* Output as an MIME folding header. */
860f0957ccaSPeter Wemm 	if ((plen = fprintf(fp, VI_DHEADER " %.*s\n",
861f0957ccaSPeter Wemm 	    FMTCOLS - (int)sizeof(VI_DHEADER), bp)) < 0)
862f0957ccaSPeter Wemm 		goto err;
863f0957ccaSPeter Wemm 	plen -= (int)sizeof(VI_DHEADER) + 1;
864f0957ccaSPeter Wemm 	for (p = bp, xlen -= plen; xlen > 0; xlen -= plen) {
865f0957ccaSPeter Wemm 		p += plen;
866f0957ccaSPeter Wemm 		if ((plen = fprintf(fp, " %.*s\n", FMTCOLS - 1, p)) < 0)
867f0957ccaSPeter Wemm 			goto err;
868f0957ccaSPeter Wemm 		plen -= 2;
869f0957ccaSPeter Wemm 	}
870f0957ccaSPeter Wemm 	FREE_SPACE(sp, bp, blen);
871f0957ccaSPeter Wemm 	return (0);
872f0957ccaSPeter Wemm 
873f0957ccaSPeter Wemm err:	FREE_SPACE(sp, bp, blen);
874f0957ccaSPeter Wemm 	return (1);
875f0957ccaSPeter Wemm alloc_err:
876f0957ccaSPeter Wemm 	msgq(sp, M_SYSERR, NULL);
877f0957ccaSPeter Wemm 	return (-1);
878f0957ccaSPeter Wemm }
879f0957ccaSPeter Wemm 
880f0957ccaSPeter Wemm /*
881f0957ccaSPeter Wemm  * rcv_dlnread --
882f0957ccaSPeter Wemm  *	Read an X-vi-data line and decode it.
883f0957ccaSPeter Wemm  */
884f0957ccaSPeter Wemm static int
885110d525eSBaptiste Daroussin rcv_dlnread(SCR *sp, char **dtypep,
886f0957ccaSPeter Wemm 	char **datap,		/* free *datap if != NULL after use. */
887f0957ccaSPeter Wemm 	FILE *fp)
888f0957ccaSPeter Wemm {
889f0957ccaSPeter Wemm 	int ch;
890f0957ccaSPeter Wemm 	char buf[1024];
891f0957ccaSPeter Wemm 	char *bp = NULL, *p, *src;
892f0957ccaSPeter Wemm 	size_t blen = 0;
893f0957ccaSPeter Wemm 	size_t len, off, dlen;
894f0957ccaSPeter Wemm 	char *dtype, *data;
895f0957ccaSPeter Wemm 	int xlen;
896f0957ccaSPeter Wemm 
897f0957ccaSPeter Wemm 	if (fgets(buf, sizeof(buf), fp) == NULL)
898f0957ccaSPeter Wemm 		return (1);
899f0957ccaSPeter Wemm 	if (strncmp(buf, VI_DHEADER, sizeof(VI_DHEADER) - 1)) {
900f0957ccaSPeter Wemm 		*dtypep = NULL;
901f0957ccaSPeter Wemm 		*datap = NULL;
902f0957ccaSPeter Wemm 		return (0);
903f0957ccaSPeter Wemm 	}
904f0957ccaSPeter Wemm 
905f0957ccaSPeter Wemm 	/* Fetch an MIME folding header. */
906f0957ccaSPeter Wemm 	len = strlen(buf) - sizeof(VI_DHEADER) + 1;
907f0957ccaSPeter Wemm 	GET_SPACE_GOTOC(sp, bp, blen, len);
908f0957ccaSPeter Wemm 	(void)memcpy(bp, buf + sizeof(VI_DHEADER) - 1, len);
909f0957ccaSPeter Wemm 	p = bp + len;
910f0957ccaSPeter Wemm 	while ((ch = fgetc(fp)) == ' ') {
911f0957ccaSPeter Wemm 		if (fgets(buf, sizeof(buf), fp) == NULL)
912f0957ccaSPeter Wemm 			goto err;
913f0957ccaSPeter Wemm 		off = strlen(buf);
914f0957ccaSPeter Wemm 		len += off;
915f0957ccaSPeter Wemm 		ADD_SPACE_GOTOC(sp, bp, blen, len);
916f0957ccaSPeter Wemm 		p = bp + len - off;
917f0957ccaSPeter Wemm 		(void)memcpy(p, buf, off);
918f0957ccaSPeter Wemm 	}
919f0957ccaSPeter Wemm 	bp[len] = '\0';
920f0957ccaSPeter Wemm 	(void)ungetc(ch, fp);
921f0957ccaSPeter Wemm 
922f0957ccaSPeter Wemm 	for (p = bp; *p == ' ' || *p == '\n'; p++);
923f0957ccaSPeter Wemm 	if ((src = strchr(p, ';')) == NULL)
924f0957ccaSPeter Wemm 		goto err;
925f0957ccaSPeter Wemm 	dlen = src - p;
926f0957ccaSPeter Wemm 	src += 1;
927f0957ccaSPeter Wemm 	len -= src - bp;
928f0957ccaSPeter Wemm 
929f0957ccaSPeter Wemm 	/* Memory looks like: "<data>\0<dtype>\0". */
930110d525eSBaptiste Daroussin 	MALLOC(sp, data, dlen + len / 4 * 3 + 2);
931f0957ccaSPeter Wemm 	if (data == NULL)
932f0957ccaSPeter Wemm 		goto err;
933f0957ccaSPeter Wemm 	if ((xlen = (b64_pton(p + dlen + 1,
934f0957ccaSPeter Wemm 	    (u_char *)data, len / 4 * 3 + 1))) == -1) {
935f0957ccaSPeter Wemm 		free(data);
936f0957ccaSPeter Wemm 		goto err;
937f0957ccaSPeter Wemm 	}
938f0957ccaSPeter Wemm 	data[xlen] = '\0';
939f0957ccaSPeter Wemm 	dtype = data + xlen + 1;
940f0957ccaSPeter Wemm 	(void)memcpy(dtype, p, dlen);
941f0957ccaSPeter Wemm 	dtype[dlen] = '\0';
942f0957ccaSPeter Wemm 	FREE_SPACE(sp, bp, blen);
943f0957ccaSPeter Wemm 	*dtypep = dtype;
944f0957ccaSPeter Wemm 	*datap = data;
945f0957ccaSPeter Wemm 	return (0);
946f0957ccaSPeter Wemm 
947f0957ccaSPeter Wemm err: 	FREE_SPACE(sp, bp, blen);
948f0957ccaSPeter Wemm 	return (1);
949f0957ccaSPeter Wemm alloc_err:
950f0957ccaSPeter Wemm 	msgq(sp, M_SYSERR, NULL);
951f0957ccaSPeter Wemm 	return (-1);
952b8ba871bSPeter Wemm }
953