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