1 /* Copyright (c) 1981 Regents of the University of California */
2 static char *sccsid = "@(#)ex3.7recover.c	7.6	07/03/83";
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 <sys/dir.h>
12 #include "uparm.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[MAXNAMLEN + 1];
199 	time_t	sf_time;
200 };
201 
202 listfiles(dirname)
203 	char *dirname;
204 {
205 	register DIR *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 = opendir(dirname);
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 ((dirent = readdir(dir)) != NULL) {
231 		if (dirent->d_name[0] != 'E')
232 			continue;
233 #ifdef DEBUG
234 		fprintf(stderr, "considering %s\n", dirent->d_name);
235 #endif
236 		/*
237 		 * Name begins with E; open it and
238 		 * make sure the uid in the header is our uid.
239 		 * If not, then don't bother with this file, it can't
240 		 * be ours.
241 		 */
242 		f = open(dirent->d_name, 0);
243 		if (f < 0) {
244 #ifdef DEBUG
245 			fprintf(stderr, "open failed\n");
246 #endif
247 			continue;
248 		}
249 		if (read(f, (char *) &H, sizeof H) != sizeof H) {
250 #ifdef DEBUG
251 			fprintf(stderr, "culdnt read hedr\n");
252 #endif
253 			ignore(close(f));
254 			continue;
255 		}
256 		ignore(close(f));
257 		if (getuid() != H.Uid) {
258 #ifdef DEBUG
259 			fprintf(stderr, "uid wrong\n");
260 #endif
261 			continue;
262 		}
263 
264 		/*
265 		 * Saved the day!
266 		 */
267 		enter(fp++, dirent->d_name, ecount);
268 		ecount++;
269 #ifdef DEBUG
270 		fprintf(stderr, "entered file %s\n", dirent->d_name);
271 #endif
272 	}
273 	ignore(closedir(dir));
274 
275 	/*
276 	 * If any files were saved, then sort them and print
277 	 * them out.
278 	 */
279 	if (ecount == 0) {
280 		fprintf(stderr, "No files saved.\n");
281 		return;
282 	}
283 	qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp);
284 	for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) {
285 		cp = ctime(&fp->sf_time);
286 		cp[10] = 0;
287 		fprintf(stderr, "On %s at ", cp);
288  		cp[16] = 0;
289 		fprintf(stderr, &cp[11]);
290 		fprintf(stderr, " saved %d lines of file \"%s\"\n",
291 		    fp->sf_lines, fp->sf_name);
292 	}
293 }
294 
295 /*
296  * Enter a new file into the saved file information.
297  */
298 enter(fp, fname, count)
299 	struct svfile *fp;
300 	char *fname;
301 {
302 	register char *cp, *cp2;
303 	register struct svfile *f, *fl;
304 	time_t curtime, itol();
305 
306 	f = 0;
307 	if (count >= NENTRY) {
308 		/*
309 		 * My god, a huge number of saved files.
310 		 * Would you work on a system that crashed this
311 		 * often?  Hope not.  So lets trash the oldest
312 		 * as the most useless.
313 		 *
314 		 * (I wonder if this code has ever run?)
315 		 */
316 		fl = fp - count + NENTRY - 1;
317 		curtime = fl->sf_time;
318 		for (f = fl; --f > fp-count; )
319 			if (f->sf_time < curtime)
320 				curtime = f->sf_time;
321 		for (f = fl; --f > fp-count; )
322 			if (f->sf_time == curtime)
323 				break;
324 		fp = f;
325 	}
326 
327 	/*
328 	 * Gotcha.
329 	 */
330 	fp->sf_time = H.Time;
331 	fp->sf_lines = H.Flines;
332 	for (cp2 = fp->sf_name, cp = savedfile; *cp;)
333 		*cp2++ = *cp++;
334 	for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;)
335 		*cp2++ = *cp++;
336 	*cp2++ = 0;
337 }
338 
339 /*
340  * Do the qsort compare to sort the entries first by file name,
341  * then by modify time.
342  */
343 qucmp(p1, p2)
344 	struct svfile *p1, *p2;
345 {
346 	register int t;
347 
348 	if (t = strcmp(p1->sf_name, p2->sf_name))
349 		return(t);
350 	if (p1->sf_time > p2->sf_time)
351 		return(-1);
352 	return(p1->sf_time < p2->sf_time);
353 }
354 
355 /*
356  * Scratch for search.
357  */
358 char	bestnb[BUFSIZ];		/* Name of the best one */
359 long	besttime;		/* Time at which the best file was saved */
360 int	bestfd;			/* Keep best file open so it dont vanish */
361 
362 /*
363  * Look for a file, both in the users directory option value
364  * (i.e. usually /tmp) and in usrpath(preserve).
365  * Want to find the newest so we search on and on.
366  */
367 findtmp(dir)
368 	char *dir;
369 {
370 
371 	/*
372 	 * No name or file so far.
373 	 */
374 	bestnb[0] = 0;
375 	bestfd = -1;
376 
377 	/*
378 	 * Search usrpath(preserve) and, if we can get there, /tmp
379 	 * (actually the users "directory" option).
380 	 */
381 	searchdir(dir);
382 	if (chdir(mydir) == 0)
383 		searchdir(mydir);
384 	if (bestfd != -1) {
385 		/*
386 		 * Gotcha.
387 		 * Put the file (which is already open) in the file
388 		 * used by the temp file routines, and save its
389 		 * name for later unlinking.
390 		 */
391 		tfile = bestfd;
392 		CP(nb, bestnb);
393 		ignorl(lseek(tfile, 0l, 0));
394 
395 		/*
396 		 * Gotta be able to read the header or fall through
397 		 * to lossage.
398 		 */
399 		if (read(tfile, (char *) &H, sizeof H) == sizeof H)
400 			return;
401 	}
402 
403 	/*
404 	 * Extreme lossage...
405 	 */
406 	error(" File not found", 0);
407 }
408 
409 /*
410  * Search for the file in directory dirname.
411  *
412  * Don't chdir here, because the users directory
413  * may be ".", and we would move away before we searched it.
414  * Note that we actually chdir elsewhere (because it is too slow
415  * to look around in usrpath(preserve) without chdir'ing there) so we
416  * can't win, because we don't know the name of '.' and if the path
417  * name of the file we want to unlink is relative, rather than absolute
418  * we won't be able to find it again.
419  */
420 searchdir(dirname)
421 	char *dirname;
422 {
423 	struct direct *dirent;
424 	register DIR *dir;
425 	char dbuf[BUFSIZ];
426 
427 	dir = opendir(dirname);
428 	if (dir == NULL)
429 		return;
430 	while ((dirent = readdir(dir)) != NULL) {
431 		if (dirent->d_name[0] != 'E')
432 			continue;
433 		/*
434 		 * Got a file in the directory starting with E...
435 		 * Save a consed up name for the file to unlink
436 		 * later, and check that this is really a file
437 		 * we are looking for.
438 		 */
439 		ignore(strcat(strcat(strcpy(nb, dirname), "/"), dirent->d_name));
440 		if (yeah(nb)) {
441 			/*
442 			 * Well, it is the file we are looking for.
443 			 * Is it more recent than any version we found before?
444 			 */
445 			if (H.Time > besttime) {
446 				/*
447 				 * A winner.
448 				 */
449 				ignore(close(bestfd));
450 				bestfd = dup(tfile);
451 				besttime = H.Time;
452 				CP(bestnb, nb);
453 			}
454 			/*
455 			 * Count versions so user can be told there are
456 			 * ``yet more pages to be turned''.
457 			 */
458 			vercnt++;
459 		}
460 		ignore(close(tfile));
461 	}
462 	ignore(closedir(dir));
463 }
464 
465 /*
466  * Given a candidate file to be recovered, see
467  * if its really an editor temporary and of this
468  * user and the file specified.
469  */
470 yeah(name)
471 	char *name;
472 {
473 
474 	tfile = open(name, 2);
475 	if (tfile < 0)
476 		return (0);
477 	if (read(tfile, (char *) &H, sizeof H) != sizeof H) {
478 nope:
479 		ignore(close(tfile));
480 		return (0);
481 	}
482 	if (!eq(savedfile, file))
483 		goto nope;
484 	if (getuid() != H.Uid)
485 		goto nope;
486 	/*
487 	 * This is old and stupid code, which
488 	 * puts a word LOST in the header block, so that lost lines
489 	 * can be made to point at it.
490 	 */
491 	ignorl(lseek(tfile, (long)(BUFSIZ*HBLKS-8), 0));
492 	ignore(write(tfile, "LOST", 5));
493 	return (1);
494 }
495 
496 preserve()
497 {
498 
499 }
500 
501 /*
502  * Find the true end of the scratch file, and ``LOSE''
503  * lines which point into thin air.  This lossage occurs
504  * due to the sandbagging of i/o which can cause blocks to
505  * be written in a non-obvious order, different from the order
506  * in which the editor tried to write them.
507  *
508  * Lines which are lost are replaced with the text LOST so
509  * they are easy to find.  We work hard at pretty formatting here
510  * as lines tend to be lost in blocks.
511  *
512  * This only seems to happen on very heavily loaded systems, and
513  * not very often.
514  */
515 scrapbad()
516 {
517 	register line *ip;
518 	struct stat stbuf;
519 	off_t size, maxt;
520 	int bno, cnt, bad, was;
521 	char bk[BUFSIZ];
522 
523 	ignore(fstat(tfile, &stbuf));
524 	size = stbuf.st_size;
525 	maxt = (size >> SHFT) | (BNDRY-1);
526 	bno = (maxt >> OFFBTS) & BLKMSK;
527 #ifdef DEBUG
528 	fprintf(stderr, "size %ld, maxt %o, bno %d\n", size, maxt, bno);
529 #endif
530 
531 	/*
532 	 * Look for a null separating two lines in the temp file;
533 	 * if last line was split across blocks, then it is lost
534 	 * if the last block is.
535 	 */
536 	while (bno > 0) {
537 		ignorl(lseek(tfile, (long) BUFSIZ * bno, 0));
538 		cnt = read(tfile, (char *) bk, BUFSIZ);
539 		while (cnt > 0)
540 			if (bk[--cnt] == 0)
541 				goto null;
542 		bno--;
543 	}
544 null:
545 
546 	/*
547 	 * Magically calculate the largest valid pointer in the temp file,
548 	 * consing it up from the block number and the count.
549 	 */
550 	maxt = ((bno << OFFBTS) | (cnt >> SHFT)) & ~1;
551 #ifdef DEBUG
552 	fprintf(stderr, "bno %d, cnt %d, maxt %o\n", bno, cnt, maxt);
553 #endif
554 
555 	/*
556 	 * Now cycle through the line pointers,
557 	 * trashing the Lusers.
558 	 */
559 	was = bad = 0;
560 	for (ip = one; ip <= dol; ip++)
561 		if (*ip > maxt) {
562 #ifdef DEBUG
563 			fprintf(stderr, "%d bad, %o > %o\n", ip - zero, *ip, maxt);
564 #endif
565 			if (was == 0)
566 				was = ip - zero;
567 			*ip = ((HBLKS*BUFSIZ)-8) >> SHFT;
568 		} else if (was) {
569 			if (bad == 0)
570 				fprintf(stderr, " [Lost line(s):");
571 			fprintf(stderr, " %d", was);
572 			if ((ip - 1) - zero > was)
573 				fprintf(stderr, "-%d", (ip - 1) - zero);
574 			bad++;
575 			was = 0;
576 		}
577 	if (was != 0) {
578 		if (bad == 0)
579 			fprintf(stderr, " [Lost line(s):");
580 		fprintf(stderr, " %d", was);
581 		if (dol - zero != was)
582 			fprintf(stderr, "-%d", dol - zero);
583 		bad++;
584 	}
585 	if (bad)
586 		fprintf(stderr, "]");
587 }
588 
589 /*
590  * Aw shucks, if we only had a (void) cast.
591  */
592 #ifdef lint
593 Ignorl(a)
594 	long a;
595 {
596 
597 	a = a;
598 }
599 
600 Ignore(a)
601 	char *a;
602 {
603 
604 	a = a;
605 }
606 
607 Ignorf(a)
608 	int (*a)();
609 {
610 
611 	a = a;
612 }
613 
614 ignorl(a)
615 	long a;
616 {
617 
618 	a = a;
619 }
620 #endif
621 
622 int	cntch, cntln, cntodd, cntnull;
623 /*
624  * Following routines stolen mercilessly from ex.
625  */
626 putfile()
627 {
628 	line *a1;
629 	register char *fp, *lp;
630 	register int nib;
631 
632 	a1 = addr1;
633 	clrstats();
634 	cntln = addr2 - a1 + 1;
635 	if (cntln == 0)
636 		return;
637 	nib = BUFSIZ;
638 	fp = genbuf;
639 	do {
640 		getline(*a1++);
641 		lp = linebuf;
642 		for (;;) {
643 			if (--nib < 0) {
644 				nib = fp - genbuf;
645 				if (write(io, genbuf, nib) != nib)
646 					wrerror();
647 				cntch += nib;
648 				nib = 511;
649 				fp = genbuf;
650 			}
651 			if ((*fp++ = *lp++) == 0) {
652 				fp[-1] = '\n';
653 				break;
654 			}
655 		}
656 	} while (a1 <= addr2);
657 	nib = fp - genbuf;
658 	if (write(io, genbuf, nib) != nib)
659 		wrerror();
660 	cntch += nib;
661 }
662 
663 wrerror()
664 {
665 
666 	syserror();
667 }
668 
669 clrstats()
670 {
671 
672 	ninbuf = 0;
673 	cntch = 0;
674 	cntln = 0;
675 	cntnull = 0;
676 	cntodd = 0;
677 }
678 
679 #define	READ	0
680 #define	WRITE	1
681 
682 getline(tl)
683 	line tl;
684 {
685 	register char *bp, *lp;
686 	register int nl;
687 
688 	lp = linebuf;
689 	bp = getblock(tl, READ);
690 	nl = nleft;
691 	tl &= ~OFFMSK;
692 	while (*lp++ = *bp++)
693 		if (--nl == 0) {
694 			bp = getblock(tl += INCRMT, READ);
695 			nl = nleft;
696 		}
697 }
698 
699 int	read();
700 int	write();
701 
702 char *
703 getblock(atl, iof)
704 	line atl;
705 	int iof;
706 {
707 	register int bno, off;
708 
709 	bno = (atl >> OFFBTS) & BLKMSK;
710 	off = (atl << SHFT) & LBTMSK;
711 	if (bno >= NMBLKS)
712 		error(" Tmp file too large");
713 	nleft = BUFSIZ - off;
714 	if (bno == iblock) {
715 		ichanged |= iof;
716 		return (ibuff + off);
717 	}
718 	if (bno == oblock)
719 		return (obuff + off);
720 	if (iof == READ) {
721 		if (ichanged)
722 			blkio(iblock, ibuff, write);
723 		ichanged = 0;
724 		iblock = bno;
725 		blkio(bno, ibuff, read);
726 		return (ibuff + off);
727 	}
728 	if (oblock >= 0)
729 		blkio(oblock, obuff, write);
730 	oblock = bno;
731 	return (obuff + off);
732 }
733 
734 blkio(b, buf, iofcn)
735 	short b;
736 	char *buf;
737 	int (*iofcn)();
738 {
739 
740 	lseek(tfile, (long) (unsigned) b * BUFSIZ, 0);
741 	if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ)
742 		syserror();
743 }
744 
745 syserror()
746 {
747 	extern int sys_nerr;
748 	extern char *sys_errlist[];
749 
750 	dirtcnt = 0;
751 	write(2, " ", 1);
752 	if (errno >= 0 && errno <= sys_nerr)
753 		error(sys_errlist[errno]);
754 	else
755 		error("System error %d", errno);
756 	exit(1);
757 }
758