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