1 /* Copyright (c) 1981 Regents of the University of California */
2 static char *sccsid = "@(#)ex3.7recover.c	7.2	07/09/81";
3 #include <stdio.h>	/* mjm: BUFSIZ: stdio = 512, VMUNIX = 1024 */
4 #undef	BUFSIZ		/* mjm: BUFSIZ different */
5 #undef	EOF		/* mjm: EOF and NULL effectively the same */
6 #undef	NULL
7 
8 #include "ex.h"
9 #include "ex_temp.h"
10 #include "ex_tty.h"
11 #include "local/uparm.h"
12 #include "sys/dir.h"
13 
14 char xstr[1];	/* make loader happy */
15 
16 /*
17  *
18  * This program searches through the specified directory and then
19  * the directory usrpath(preserve) looking for an instance of the specified
20  * file from a crashed editor or a crashed system.
21  * If this file is found, it is unscrambled and written to
22  * the standard output.
23  *
24  * If this program terminates without a "broken pipe" diagnostic
25  * (i.e. the editor doesn't die right away) then the buffer we are
26  * writing from is removed when we finish.  This is potentially a mistake
27  * as there is not enough handshaking to guarantee that the file has actually
28  * been recovered, but should suffice for most cases.
29  */
30 
31 /*
32  * For lint's sake...
33  */
34 #ifndef lint
35 #define	ignorl(a)	a
36 #endif
37 
38 /*
39  * This directory definition also appears (obviously) in expreserve.c.
40  * Change both if you change either.
41  */
42 char	mydir[] =	usrpath(preserve);
43 
44 /*
45  * Limit on the number of printed entries
46  * when an, e.g. ``ex -r'' command is given.
47  */
48 #define	NENTRY	50
49 
50 char	*ctime();
51 char	nb[BUFSIZ];
52 int	vercnt;			/* Count number of versions of file found */
53 
54 main(argc, argv)
55 	int argc;
56 	char *argv[];
57 {
58 	register char *cp;
59 	register int b, i;
60 
61 	/*
62 	 * Initialize as though the editor had just started.
63 	 */
64 	fendcore = (line *) sbrk(0);
65 	dot = zero = dol = fendcore;
66 	one = zero + 1;
67 	endcore = fendcore - 2;
68 	iblock = oblock = -1;
69 
70 	/*
71 	 * If given only a -r argument, then list the saved files.
72 	 */
73 	if (argc == 2 && eq(argv[1], "-r")) {
74 		listfiles(mydir);
75 		exit(0);
76 	}
77 	if (argc != 3)
78 		error(" Wrong number of arguments to exrecover", 0);
79 
80 	CP(file, argv[2]);
81 
82 	/*
83 	 * Search for this file.
84 	 */
85 	findtmp(argv[1]);
86 
87 	/*
88 	 * Got (one of the versions of) it, write it back to the editor.
89 	 */
90 	cp = ctime(&H.Time);
91 	cp[19] = 0;
92 	fprintf(stderr, " [Dated: %s", cp);
93 	fprintf(stderr, vercnt > 1 ? ", newest of %d saved]" : "]", vercnt);
94 	H.Flines++;
95 
96 	/*
97 	 * Allocate space for the line pointers from the temp file.
98 	 */
99 	if ((int) sbrk((int) (H.Flines * sizeof (line))) == -1)
100 		/*
101 		 * Good grief.
102 		 */
103 		error(" Not enough core for lines", 0);
104 #ifdef DEBUG
105 	fprintf(stderr, "%d lines\n", H.Flines);
106 #endif
107 
108 	/*
109 	 * Now go get the blocks of seek pointers which are scattered
110 	 * throughout the temp file, reconstructing the incore
111 	 * line pointers at point of crash.
112 	 */
113 	b = 0;
114 	while (H.Flines > 0) {
115 		ignorl(lseek(tfile, (long) blocks[b] * BUFSIZ, 0));
116 		i = H.Flines < BUFSIZ / sizeof (line) ?
117 			H.Flines * sizeof (line) : BUFSIZ;
118 		if (read(tfile, (char *) dot, i) != i) {
119 			perror(nb);
120 			exit(1);
121 		}
122 		dot += i / sizeof (line);
123 		H.Flines -= i / sizeof (line);
124 		b++;
125 	}
126 	dot--; dol = dot;
127 
128 	/*
129 	 * Sigh... due to sandbagging some lines may really not be there.
130 	 * Find and discard such.  This shouldn't happen much.
131 	 */
132 	scrapbad();
133 
134 	/*
135 	 * Now if there were any lines in the recovered file
136 	 * write them to the standard output.
137 	 */
138 	if (dol > zero) {
139 		addr1 = one; addr2 = dol; io = 1;
140 		putfile(0);
141 	}
142 
143 	/*
144 	 * Trash the saved buffer.
145 	 * Hopefully the system won't crash before the editor
146 	 * syncs the new recovered buffer; i.e. for an instant here
147 	 * you may lose if the system crashes because this file
148 	 * is gone, but the editor hasn't completed reading the recovered
149 	 * file from the pipe from us to it.
150 	 *
151 	 * This doesn't work if we are coming from an non-absolute path
152 	 * name since we may have chdir'ed but what the hay, noone really
153 	 * ever edits with temporaries in "." anyways.
154 	 */
155 	if (nb[0] == '/')
156 		ignore(unlink(nb));
157 
158 	/*
159 	 * Adieu.
160 	 */
161 	exit(0);
162 }
163 
164 /*
165  * Print an error message (notably not in error
166  * message file).  If terminal is in RAW mode, then
167  * we should be writing output for "vi", so don't print
168  * a newline which would screw up the screen.
169  */
170 /*VARARGS2*/
171 error(str, inf)
172 	char *str;
173 	int inf;
174 {
175 
176 	fprintf(stderr, str, inf);
177 #ifndef USG3TTY
178 	gtty(2, &tty);
179 	if ((tty.sg_flags & RAW) == 0)
180 #else
181 	ioctl(2, TCGETA, &tty);
182 	if (tty.c_lflag & ICANON)
183 #endif
184 		fprintf(stderr, "\n");
185 	exit(1);
186 }
187 
188 /*
189  * Here we save the information about files, when
190  * you ask us what files we have saved for you.
191  * We buffer file name, number of lines, and the time
192  * at which the file was saved.
193  */
194 struct svfile {
195 	char	sf_name[FNSIZE + 1];
196 	int	sf_lines;
197 	char	sf_entry[DIRSIZ + 1];
198 	time_t	sf_time;
199 };
200 
201 listfiles(dirname)
202 	char *dirname;
203 {
204 	register FILE *dir;
205 	struct direct dirent;
206 	int ecount, qucmp();
207 	register int f;
208 	char *cp;
209 	struct svfile *fp, svbuf[NENTRY];
210 
211 	/*
212 	 * Open usrpath(preserve), and go there to make things quick.
213 	 */
214 	dir = fopen(dirname, "r");
215 	if (dir == NULL) {
216 		perror(dirname);
217 		return;
218 	}
219 	if (chdir(dirname) < 0) {
220 		perror(dirname);
221 		return;
222 	}
223 
224 	/*
225 	 * Look at the candidate files in usrpath(preserve).
226 	 */
227 	fp = &svbuf[0];
228 	ecount = 0;
229 	while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) {
230 		if (dirent.d_ino == 0)
231 			continue;
232 		if (dirent.d_name[0] != 'E')
233 			continue;
234 #ifdef DEBUG
235 		fprintf(stderr, "considering %s\n", dirent.d_name);
236 #endif
237 		/*
238 		 * Name begins with E; open it and
239 		 * make sure the uid in the header is our uid.
240 		 * If not, then don't bother with this file, it can't
241 		 * be ours.
242 		 */
243 		f = open(dirent.d_name, 0);
244 		if (f < 0) {
245 #ifdef DEBUG
246 			fprintf(stderr, "open failed\n");
247 #endif
248 			continue;
249 		}
250 		if (read(f, (char *) &H, sizeof H) != sizeof H) {
251 #ifdef DEBUG
252 			fprintf(stderr, "culdnt read hedr\n");
253 #endif
254 			ignore(close(f));
255 			continue;
256 		}
257 		ignore(close(f));
258 		if (getuid() != H.Uid) {
259 #ifdef DEBUG
260 			fprintf(stderr, "uid wrong\n");
261 #endif
262 			continue;
263 		}
264 
265 		/*
266 		 * Saved the day!
267 		 */
268 		enter(fp++, dirent.d_name, ecount);
269 		ecount++;
270 #ifdef DEBUG
271 		fprintf(stderr, "entered file %s\n", dirent.d_name);
272 #endif
273 	}
274 	ignore(fclose(dir));
275 
276 	/*
277 	 * If any files were saved, then sort them and print
278 	 * them out.
279 	 */
280 	if (ecount == 0) {
281 		fprintf(stderr, "No files saved.\n");
282 		return;
283 	}
284 	qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp);
285 	for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) {
286 		cp = ctime(&fp->sf_time);
287 		cp[10] = 0;
288 		fprintf(stderr, "On %s at ", cp);
289  		cp[16] = 0;
290 		fprintf(stderr, &cp[11]);
291 		fprintf(stderr, " saved %d lines of file \"%s\"\n",
292 		    fp->sf_lines, fp->sf_name);
293 	}
294 }
295 
296 /*
297  * Enter a new file into the saved file information.
298  */
299 enter(fp, fname, count)
300 	struct svfile *fp;
301 	char *fname;
302 {
303 	register char *cp, *cp2;
304 	register struct svfile *f, *fl;
305 	time_t curtime, itol();
306 
307 	f = 0;
308 	if (count >= NENTRY) {
309 		/*
310 		 * My god, a huge number of saved files.
311 		 * Would you work on a system that crashed this
312 		 * often?  Hope not.  So lets trash the oldest
313 		 * as the most useless.
314 		 *
315 		 * (I wonder if this code has ever run?)
316 		 */
317 		fl = fp - count + NENTRY - 1;
318 		curtime = fl->sf_time;
319 		for (f = fl; --f > fp-count; )
320 			if (f->sf_time < curtime)
321 				curtime = f->sf_time;
322 		for (f = fl; --f > fp-count; )
323 			if (f->sf_time == curtime)
324 				break;
325 		fp = f;
326 	}
327 
328 	/*
329 	 * Gotcha.
330 	 */
331 	fp->sf_time = H.Time;
332 	fp->sf_lines = H.Flines;
333 	for (cp2 = fp->sf_name, cp = savedfile; *cp;)
334 		*cp2++ = *cp++;
335 	for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;)
336 		*cp2++ = *cp++;
337 	*cp2++ = 0;
338 }
339 
340 /*
341  * Do the qsort compare to sort the entries first by file name,
342  * then by modify time.
343  */
344 qucmp(p1, p2)
345 	struct svfile *p1, *p2;
346 {
347 	register int t;
348 
349 	if (t = strcmp(p1->sf_name, p2->sf_name))
350 		return(t);
351 	if (p1->sf_time > p2->sf_time)
352 		return(-1);
353 	return(p1->sf_time < p2->sf_time);
354 }
355 
356 /*
357  * Scratch for search.
358  */
359 char	bestnb[BUFSIZ];		/* Name of the best one */
360 long	besttime;		/* Time at which the best file was saved */
361 int	bestfd;			/* Keep best file open so it dont vanish */
362 
363 /*
364  * Look for a file, both in the users directory option value
365  * (i.e. usually /tmp) and in usrpath(preserve).
366  * Want to find the newest so we search on and on.
367  */
368 findtmp(dir)
369 	char *dir;
370 {
371 
372 	/*
373 	 * No name or file so far.
374 	 */
375 	bestnb[0] = 0;
376 	bestfd = -1;
377 
378 	/*
379 	 * Search usrpath(preserve) and, if we can get there, /tmp
380 	 * (actually the users "directory" option).
381 	 */
382 	searchdir(dir);
383 	if (chdir(mydir) == 0)
384 		searchdir(mydir);
385 	if (bestfd != -1) {
386 		/*
387 		 * Gotcha.
388 		 * Put the file (which is already open) in the file
389 		 * used by the temp file routines, and save its
390 		 * name for later unlinking.
391 		 */
392 		tfile = bestfd;
393 		CP(nb, bestnb);
394 		ignorl(lseek(tfile, 0l, 0));
395 
396 		/*
397 		 * Gotta be able to read the header or fall through
398 		 * to lossage.
399 		 */
400 		if (read(tfile, (char *) &H, sizeof H) == sizeof H)
401 			return;
402 	}
403 
404 	/*
405 	 * Extreme lossage...
406 	 */
407 	error(" File not found", 0);
408 }
409 
410 /*
411  * Search for the file in directory dirname.
412  *
413  * Don't chdir here, because the users directory
414  * may be ".", and we would move away before we searched it.
415  * Note that we actually chdir elsewhere (because it is too slow
416  * to look around in usrpath(preserve) without chdir'ing there) so we
417  * can't win, because we don't know the name of '.' and if the path
418  * name of the file we want to unlink is relative, rather than absolute
419  * we won't be able to find it again.
420  */
421 searchdir(dirname)
422 	char *dirname;
423 {
424 	struct direct dirent;
425 	register FILE *dir;
426 	char dbuf[BUFSIZ];
427 
428 	dir = fopen(dirname, "r");
429 	if (dir == NULL)
430 		return;
431 	setbuf(dir, dbuf);
432 	while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) {
433 		if (dirent.d_ino == 0)
434 			continue;
435 		if (dirent.d_name[0] != 'E' || dirent.d_name[DIRSIZ - 1] != 0)
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(fclose(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 	fprintf(stderr, "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 	fprintf(stderr, "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 			fprintf(stderr, "%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 				fprintf(stderr, " [Lost line(s):");
575 			fprintf(stderr, " %d", was);
576 			if ((ip - 1) - zero > was)
577 				fprintf(stderr, "-%d", (ip - 1) - zero);
578 			bad++;
579 			was = 0;
580 		}
581 	if (was != 0) {
582 		if (bad == 0)
583 			fprintf(stderr, " [Lost line(s):");
584 		fprintf(stderr, " %d", was);
585 		if (dol - zero != was)
586 			fprintf(stderr, "-%d", dol - zero);
587 		bad++;
588 	}
589 	if (bad)
590 		fprintf(stderr, "]");
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 	extern int sys_nerr;
752 	extern char *sys_errlist[];
753 
754 	dirtcnt = 0;
755 	write(2, " ", 1);
756 	if (errno >= 0 && errno <= sys_nerr)
757 		error(sys_errlist[errno]);
758 	else
759 		error("System error %d", errno);
760 	exit(1);
761 }
762