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