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