GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/vi/build/../common/recover.c Lines: 0 301 0.0 %
Date: 2017-11-13 Branches: 0 239 0.0 %

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