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

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