1 /*-
2  * Copyright (c) 1980 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.proprietary.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1980 The Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)ex3.7recover.c	7.14 (Berkeley) 04/17/91";
16 #endif /* not lint */
17 
18 #include <stdio.h>	/* mjm: BUFSIZ: stdio = 512, VMUNIX = 1024 */
19 #undef	BUFSIZ		/* mjm: BUFSIZ different */
20 #undef	EOF		/* mjm: EOF and NULL effectively the same */
21 #undef	NULL
22 
23 #include "ex.h"
24 #include "ex_temp.h"
25 #include "ex_tty.h"
26 #include <sys/dir.h>
27 #include <stdarg.h>
28 
29 char xstr[1];		/* make loader happy */
30 short tfile = -1;	/* ditto */
31 
32 /*
33  *
34  * This program searches through the specified directory and then
35  * the directory _PATH_USRPRESERVE looking for an instance of the specified
36  * file from a crashed editor or a crashed system.
37  * If this file is found, it is unscrambled and written to
38  * the standard output.
39  *
40  * If this program terminates without a "broken pipe" diagnostic
41  * (i.e. the editor doesn't die right away) then the buffer we are
42  * writing from is removed when we finish.  This is potentially a mistake
43  * as there is not enough handshaking to guarantee that the file has actually
44  * been recovered, but should suffice for most cases.
45  */
46 
47 /*
48  * For lint's sake...
49  */
50 #ifndef lint
51 #define	ignorl(a)	a
52 #endif
53 
54 /*
55  * Limit on the number of printed entries
56  * when an, e.g. ``ex -r'' command is given.
57  */
58 #define	NENTRY	50
59 
60 char	*ctime();
61 char	nb[BUFSIZ];
62 int	vercnt;			/* Count number of versions of file found */
63 
64 main(argc, argv)
65 	int argc;
66 	char *argv[];
67 {
68 	register char *cp;
69 	register int b, i;
70 
71 	/*
72 	 * Initialize as though the editor had just started.
73 	 */
74 	fendcore = (line *) sbrk(0);
75 	dot = zero = dol = fendcore;
76 	one = zero + 1;
77 	endcore = fendcore - 2;
78 	iblock = oblock = -1;
79 
80 	/*
81 	 * If given only a -r argument, then list the saved files.
82 	 */
83 	if (argc == 2 && eq(argv[1], "-r")) {
84 		listfiles(_PATH_PRESERVE);
85 		exit(0);
86 	}
87 	if (argc != 3)
88 		error(" Wrong number of arguments to exrecover", 0);
89 
90 	CP(file, argv[2]);
91 
92 	/*
93 	 * Search for this file.
94 	 */
95 	findtmp(argv[1]);
96 
97 	/*
98 	 * Got (one of the versions of) it, write it back to the editor.
99 	 */
100 	cp = ctime(&H.Time);
101 	cp[19] = 0;
102 	fpr(" [Dated: %s", cp);
103 	fpr(vercnt > 1 ? ", newest of %d saved]" : "]", vercnt);
104 	H.Flines++;
105 
106 	/*
107 	 * Allocate space for the line pointers from the temp file.
108 	 */
109 	if ((int) sbrk((int) (H.Flines * sizeof (line))) == -1)
110 		/*
111 		 * Good grief.
112 		 */
113 		error(" Not enough core for lines", 0);
114 #ifdef DEBUG
115 	fpr("%d lines\n", H.Flines);
116 #endif
117 
118 	/*
119 	 * Now go get the blocks of seek pointers which are scattered
120 	 * throughout the temp file, reconstructing the incore
121 	 * line pointers at point of crash.
122 	 */
123 	b = 0;
124 	while (H.Flines > 0) {
125 		ignorl(lseek(tfile, (long) blocks[b] * BUFSIZ, 0));
126 		i = H.Flines < BUFSIZ / sizeof (line) ?
127 			H.Flines * sizeof (line) : BUFSIZ;
128 		if (read(tfile, (char *) dot, i) != i) {
129 			perror(nb);
130 			exit(1);
131 		}
132 		dot += i / sizeof (line);
133 		H.Flines -= i / sizeof (line);
134 		b++;
135 	}
136 	dot--; dol = dot;
137 
138 	/*
139 	 * Sigh... due to sandbagging some lines may really not be there.
140 	 * Find and discard such.  This shouldn't happen much.
141 	 */
142 	scrapbad();
143 
144 	/*
145 	 * Now if there were any lines in the recovered file
146 	 * write them to the standard output.
147 	 */
148 	if (dol > zero) {
149 		addr1 = one; addr2 = dol; io = 1;
150 		putfile(0);
151 	}
152 
153 	/*
154 	 * Trash the saved buffer.
155 	 * Hopefully the system won't crash before the editor
156 	 * syncs the new recovered buffer; i.e. for an instant here
157 	 * you may lose if the system crashes because this file
158 	 * is gone, but the editor hasn't completed reading the recovered
159 	 * file from the pipe from us to it.
160 	 *
161 	 * This doesn't work if we are coming from an non-absolute path
162 	 * name since we may have chdir'ed but what the hay, noone really
163 	 * ever edits with temporaries in "." anyways.
164 	 */
165 	if (nb[0] == '/')
166 		ignore(unlink(nb));
167 
168 	/*
169 	 * Adieu.
170 	 */
171 	exit(0);
172 }
173 
174 /*
175  * Print an error message (notably not in error
176  * message file).  If terminal is in RAW mode, then
177  * we should be writing output for "vi", so don't print
178  * a newline which would screw up the screen.
179  */
180 /*VARARGS2*/
181 error(str, inf)
182 	char *str;
183 	int inf;
184 {
185 
186 	fpr(str, inf);
187 	(void)ioctl(2, TIOCGETP, &tty);
188 	if ((tty.sg_flags & RAW) == 0)
189 		fpr("\n");
190 	exit(1);
191 }
192 
193 /*
194  * Here we save the information about files, when
195  * you ask us what files we have saved for you.
196  * We buffer file name, number of lines, and the time
197  * at which the file was saved.
198  */
199 struct svfile {
200 	char	sf_name[FNSIZE + 1];
201 	int	sf_lines;
202 	char	sf_entry[MAXNAMLEN + 1];
203 	time_t	sf_time;
204 };
205 
206 listfiles(dirname)
207 	char *dirname;
208 {
209 	register DIR *dir;
210 	struct direct *dirent;
211 	int ecount, qucmp();
212 	register int f;
213 	char *cp;
214 	struct svfile *fp, svbuf[NENTRY];
215 
216 	/*
217 	 * Open _PATH_PRESERVE, and go there to make things quick.
218 	 */
219 	dir = opendir(dirname);
220 	if (dir == NULL) {
221 		perror(dirname);
222 		return;
223 	}
224 	if (chdir(dirname) < 0) {
225 		perror(dirname);
226 		return;
227 	}
228 
229 	/*
230 	 * Look at the candidate files in _PATH_PRESERVE.
231 	 */
232 	fp = &svbuf[0];
233 	ecount = 0;
234 	while ((dirent = readdir(dir)) != NULL) {
235 		if (dirent->d_name[0] != 'E')
236 			continue;
237 #ifdef DEBUG
238 		fpr("considering %s\n", dirent->d_name);
239 #endif
240 		/*
241 		 * Name begins with E; open it and
242 		 * make sure the uid in the header is our uid.
243 		 * If not, then don't bother with this file, it can't
244 		 * be ours.
245 		 */
246 		f = open(dirent->d_name, 0);
247 		if (f < 0) {
248 #ifdef DEBUG
249 			fpr("open failed\n");
250 #endif
251 			continue;
252 		}
253 		if (read(f, (char *) &H, sizeof H) != sizeof H) {
254 #ifdef DEBUG
255 			fpr("culdnt read hedr\n");
256 #endif
257 			ignore(close(f));
258 			continue;
259 		}
260 		ignore(close(f));
261 		if (getuid() != H.Uid) {
262 #ifdef DEBUG
263 			fpr("uid wrong\n");
264 #endif
265 			continue;
266 		}
267 
268 		/*
269 		 * Saved the day!
270 		 */
271 		enter(fp++, dirent->d_name, ecount);
272 		ecount++;
273 #ifdef DEBUG
274 		fpr("entered file %s\n", dirent->d_name);
275 #endif
276 	}
277 	ignore(closedir(dir));
278 
279 	/*
280 	 * If any files were saved, then sort them and print
281 	 * them out.
282 	 */
283 	if (ecount == 0) {
284 		fpr("No files saved.\n");
285 		return;
286 	}
287 	qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp);
288 	for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) {
289 		cp = ctime(&fp->sf_time);
290 		cp[10] = 0;
291 		fpr("On %s at ", cp);
292  		cp[16] = 0;
293 		fpr(&cp[11]);
294 		fpr(" saved %d lines of file \"%s\"\n",
295 		    fp->sf_lines, fp->sf_name);
296 	}
297 }
298 
299 /*
300  * Enter a new file into the saved file information.
301  */
302 enter(fp, fname, count)
303 	struct svfile *fp;
304 	char *fname;
305 {
306 	register char *cp, *cp2;
307 	register struct svfile *f, *fl;
308 	time_t curtime, itol();
309 
310 	f = 0;
311 	if (count >= NENTRY) {
312 		/*
313 		 * My god, a huge number of saved files.
314 		 * Would you work on a system that crashed this
315 		 * often?  Hope not.  So lets trash the oldest
316 		 * as the most useless.
317 		 *
318 		 * (I wonder if this code has ever run?)
319 		 */
320 		fl = fp - count + NENTRY - 1;
321 		curtime = fl->sf_time;
322 		for (f = fl; --f > fp-count; )
323 			if (f->sf_time < curtime)
324 				curtime = f->sf_time;
325 		for (f = fl; --f > fp-count; )
326 			if (f->sf_time == curtime)
327 				break;
328 		fp = f;
329 	}
330 
331 	/*
332 	 * Gotcha.
333 	 */
334 	fp->sf_time = H.Time;
335 	fp->sf_lines = H.Flines;
336 	for (cp2 = fp->sf_name, cp = savedfile; *cp;)
337 		*cp2++ = *cp++;
338 	for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;)
339 		*cp2++ = *cp++;
340 	*cp2++ = 0;
341 }
342 
343 /*
344  * Do the qsort compare to sort the entries first by file name,
345  * then by modify time.
346  */
347 qucmp(p1, p2)
348 	struct svfile *p1, *p2;
349 {
350 	register int t;
351 
352 	if (t = strcmp(p1->sf_name, p2->sf_name))
353 		return(t);
354 	if (p1->sf_time > p2->sf_time)
355 		return(-1);
356 	return(p1->sf_time < p2->sf_time);
357 }
358 
359 /*
360  * Scratch for search.
361  */
362 char	bestnb[BUFSIZ];		/* Name of the best one */
363 long	besttime;		/* Time at which the best file was saved */
364 int	bestfd;			/* Keep best file open so it dont vanish */
365 
366 /*
367  * Look for a file, both in the users directory option value
368  * (i.e. usually /tmp) and in _PATH_PRESERVE.
369  * Want to find the newest so we search on and on.
370  */
371 findtmp(dir)
372 	char *dir;
373 {
374 
375 	/*
376 	 * No name or file so far.
377 	 */
378 	bestnb[0] = 0;
379 	bestfd = -1;
380 
381 	/*
382 	 * Search _PATH_PRESERVE and, if we can get there, /tmp
383 	 * (actually the users "directory" option).
384 	 */
385 	searchdir(dir);
386 	if (chdir(_PATH_PRESERVE) == 0)
387 		searchdir(_PATH_PRESERVE);
388 	if (bestfd != -1) {
389 		/*
390 		 * Gotcha.
391 		 * Put the file (which is already open) in the file
392 		 * used by the temp file routines, and save its
393 		 * name for later unlinking.
394 		 */
395 		tfile = bestfd;
396 		CP(nb, bestnb);
397 		ignorl(lseek(tfile, 0l, 0));
398 
399 		/*
400 		 * Gotta be able to read the header or fall through
401 		 * to lossage.
402 		 */
403 		if (read(tfile, (char *) &H, sizeof H) == sizeof H)
404 			return;
405 	}
406 
407 	/*
408 	 * Extreme lossage...
409 	 */
410 	error(" File not found", 0);
411 }
412 
413 /*
414  * Search for the file in directory dirname.
415  *
416  * Don't chdir here, because the users directory
417  * may be ".", and we would move away before we searched it.
418  * Note that we actually chdir elsewhere (because it is too slow
419  * to look around in _PATH_PRESERVE without chdir'ing there) so we
420  * can't win, because we don't know the name of '.' and if the path
421  * name of the file we want to unlink is relative, rather than absolute
422  * we won't be able to find it again.
423  */
424 searchdir(dirname)
425 	char *dirname;
426 {
427 	struct direct *dirent;
428 	register DIR *dir;
429 	char dbuf[BUFSIZ];
430 
431 	dir = opendir(dirname);
432 	if (dir == NULL)
433 		return;
434 	while ((dirent = readdir(dir)) != NULL) {
435 		if (dirent->d_name[0] != 'E')
436 			continue;
437 		/*
438 		 * Got a file in the directory starting with E...
439 		 * Save a consed up name for the file to unlink
440 		 * later, and check that this is really a file
441 		 * we are looking for.
442 		 */
443 		ignore(strcat(strcat(strcpy(nb, dirname), "/"), dirent->d_name));
444 		if (yeah(nb)) {
445 			/*
446 			 * Well, it is the file we are looking for.
447 			 * Is it more recent than any version we found before?
448 			 */
449 			if (H.Time > besttime) {
450 				/*
451 				 * A winner.
452 				 */
453 				ignore(close(bestfd));
454 				bestfd = dup(tfile);
455 				besttime = H.Time;
456 				CP(bestnb, nb);
457 			}
458 			/*
459 			 * Count versions so user can be told there are
460 			 * ``yet more pages to be turned''.
461 			 */
462 			vercnt++;
463 		}
464 		ignore(close(tfile));
465 	}
466 	ignore(closedir(dir));
467 }
468 
469 /*
470  * Given a candidate file to be recovered, see
471  * if its really an editor temporary and of this
472  * user and the file specified.
473  */
474 yeah(name)
475 	char *name;
476 {
477 
478 	tfile = open(name, 2);
479 	if (tfile < 0)
480 		return (0);
481 	if (read(tfile, (char *) &H, sizeof H) != sizeof H) {
482 nope:
483 		ignore(close(tfile));
484 		return (0);
485 	}
486 	if (!eq(savedfile, file))
487 		goto nope;
488 	if (getuid() != H.Uid)
489 		goto nope;
490 	/*
491 	 * This is old and stupid code, which
492 	 * puts a word LOST in the header block, so that lost lines
493 	 * can be made to point at it.
494 	 */
495 	ignorl(lseek(tfile, (long)(BUFSIZ*HBLKS-8), 0));
496 	ignore(write(tfile, "LOST", 5));
497 	return (1);
498 }
499 
500 preserve()
501 {
502 
503 }
504 
505 /*
506  * Find the true end of the scratch file, and ``LOSE''
507  * lines which point into thin air.  This lossage occurs
508  * due to the sandbagging of i/o which can cause blocks to
509  * be written in a non-obvious order, different from the order
510  * in which the editor tried to write them.
511  *
512  * Lines which are lost are replaced with the text LOST so
513  * they are easy to find.  We work hard at pretty formatting here
514  * as lines tend to be lost in blocks.
515  *
516  * This only seems to happen on very heavily loaded systems, and
517  * not very often.
518  */
519 scrapbad()
520 {
521 	register line *ip;
522 	struct stat stbuf;
523 	off_t size, maxt;
524 	int bno, cnt, bad, was;
525 	char bk[BUFSIZ];
526 
527 	ignore(fstat(tfile, &stbuf));
528 	size = stbuf.st_size;
529 	maxt = (size >> SHFT) | (BNDRY-1);
530 	bno = (maxt >> OFFBTS) & BLKMSK;
531 #ifdef DEBUG
532 	fpr("size %ld, maxt %o, bno %d\n", size, maxt, bno);
533 #endif
534 
535 	/*
536 	 * Look for a null separating two lines in the temp file;
537 	 * if last line was split across blocks, then it is lost
538 	 * if the last block is.
539 	 */
540 	while (bno > 0) {
541 		ignorl(lseek(tfile, (long) BUFSIZ * bno, 0));
542 		cnt = read(tfile, (char *) bk, BUFSIZ);
543 		while (cnt > 0)
544 			if (bk[--cnt] == 0)
545 				goto null;
546 		bno--;
547 	}
548 null:
549 
550 	/*
551 	 * Magically calculate the largest valid pointer in the temp file,
552 	 * consing it up from the block number and the count.
553 	 */
554 	maxt = ((bno << OFFBTS) | (cnt >> SHFT)) & ~1;
555 #ifdef DEBUG
556 	fpr("bno %d, cnt %d, maxt %o\n", bno, cnt, maxt);
557 #endif
558 
559 	/*
560 	 * Now cycle through the line pointers,
561 	 * trashing the Lusers.
562 	 */
563 	was = bad = 0;
564 	for (ip = one; ip <= dol; ip++)
565 		if (*ip > maxt) {
566 #ifdef DEBUG
567 			fpr("%d bad, %o > %o\n", ip - zero, *ip, maxt);
568 #endif
569 			if (was == 0)
570 				was = ip - zero;
571 			*ip = ((HBLKS*BUFSIZ)-8) >> SHFT;
572 		} else if (was) {
573 			if (bad == 0)
574 				fpr(" [Lost line(s):");
575 			fpr(" %d", was);
576 			if ((ip - 1) - zero > was)
577 				fpr("-%d", (ip - 1) - zero);
578 			bad++;
579 			was = 0;
580 		}
581 	if (was != 0) {
582 		if (bad == 0)
583 			fpr(" [Lost line(s):");
584 		fpr(" %d", was);
585 		if (dol - zero != was)
586 			fpr("-%d", dol - zero);
587 		bad++;
588 	}
589 	if (bad)
590 		fpr("]");
591 }
592 
593 /*
594  * Aw shucks, if we only had a (void) cast.
595  */
596 #ifdef lint
597 Ignorl(a)
598 	long a;
599 {
600 
601 	a = a;
602 }
603 
604 Ignore(a)
605 	char *a;
606 {
607 
608 	a = a;
609 }
610 
611 Ignorf(a)
612 	int (*a)();
613 {
614 
615 	a = a;
616 }
617 
618 ignorl(a)
619 	long a;
620 {
621 
622 	a = a;
623 }
624 #endif
625 
626 int	cntch, cntln, cntodd, cntnull;
627 /*
628  * Following routines stolen mercilessly from ex.
629  */
630 putfile()
631 {
632 	line *a1;
633 	register char *fp, *lp;
634 	register int nib;
635 
636 	a1 = addr1;
637 	clrstats();
638 	cntln = addr2 - a1 + 1;
639 	if (cntln == 0)
640 		return;
641 	nib = BUFSIZ;
642 	fp = genbuf;
643 	do {
644 		getline(*a1++);
645 		lp = linebuf;
646 		for (;;) {
647 			if (--nib < 0) {
648 				nib = fp - genbuf;
649 				if (write(io, genbuf, nib) != nib)
650 					wrerror();
651 				cntch += nib;
652 				nib = 511;
653 				fp = genbuf;
654 			}
655 			if ((*fp++ = *lp++) == 0) {
656 				fp[-1] = '\n';
657 				break;
658 			}
659 		}
660 	} while (a1 <= addr2);
661 	nib = fp - genbuf;
662 	if (write(io, genbuf, nib) != nib)
663 		wrerror();
664 	cntch += nib;
665 }
666 
667 wrerror()
668 {
669 
670 	syserror();
671 }
672 
673 clrstats()
674 {
675 
676 	ninbuf = 0;
677 	cntch = 0;
678 	cntln = 0;
679 	cntnull = 0;
680 	cntodd = 0;
681 }
682 
683 #define	READ	0
684 #define	WRITE	1
685 
686 getline(tl)
687 	line tl;
688 {
689 	register char *bp, *lp;
690 	register int nl;
691 
692 	lp = linebuf;
693 	bp = getblock(tl, READ);
694 	nl = nleft;
695 	tl &= ~OFFMSK;
696 	while (*lp++ = *bp++)
697 		if (--nl == 0) {
698 			bp = getblock(tl += INCRMT, READ);
699 			nl = nleft;
700 		}
701 }
702 
703 int	read();
704 int	write();
705 
706 char *
707 getblock(atl, iof)
708 	line atl;
709 	int iof;
710 {
711 	register int bno, off;
712 
713 	bno = (atl >> OFFBTS) & BLKMSK;
714 	off = (atl << SHFT) & LBTMSK;
715 	if (bno >= NMBLKS)
716 		error(" Tmp file too large");
717 	nleft = BUFSIZ - off;
718 	if (bno == iblock) {
719 		ichanged |= iof;
720 		return (ibuff + off);
721 	}
722 	if (bno == oblock)
723 		return (obuff + off);
724 	if (iof == READ) {
725 		if (ichanged)
726 			blkio(iblock, ibuff, write);
727 		ichanged = 0;
728 		iblock = bno;
729 		blkio(bno, ibuff, read);
730 		return (ibuff + off);
731 	}
732 	if (oblock >= 0)
733 		blkio(oblock, obuff, write);
734 	oblock = bno;
735 	return (obuff + off);
736 }
737 
738 blkio(b, buf, iofcn)
739 	short b;
740 	char *buf;
741 	int (*iofcn)();
742 {
743 
744 	lseek(tfile, (long) (unsigned) b * BUFSIZ, 0);
745 	if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ)
746 		syserror();
747 }
748 
749 syserror()
750 {
751 	char *strerror();
752 
753 	dirtcnt = 0;
754 	write(2, " ", 1);
755 	error(strerror(errno));
756 	exit(1);
757 }
758 
759 /*
760  * Must avoid stdio because expreserve uses sbrk to do memory
761  * allocation and stdio uses malloc.
762  */
763 fpr(fmt)
764 	char *fmt;
765 {
766 	va_list ap;
767 	int len;
768 	char buf[BUFSIZ];
769 
770 	va_start(ap, fmt);
771 	len = vsnprintf(buf, sizeof(buf), fmt, ap);
772 	va_end(ap);
773 	write(2, buf, len);
774 }
775