1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  */
6 
7 #ifndef lint
8 char *copyright =
9 "@(#) Copyright (c) 1980 Regents of the University of California.\n\
10  All rights reserved.\n";
11 #endif not lint
12 
13 #ifndef lint
14 static char *sccsid = "@(#)ex3.7recover.c	7.13 (Berkeley) 03/01/91";
15 #endif not lint
16 
17 #include <stdio.h>	/* mjm: BUFSIZ: stdio = 512, VMUNIX = 1024 */
18 #undef	BUFSIZ		/* mjm: BUFSIZ different */
19 #undef	EOF		/* mjm: EOF and NULL effectively the same */
20 #undef	NULL
21 
22 #include "ex.h"
23 #include "ex_temp.h"
24 #include "ex_tty.h"
25 #include <sys/dir.h>
26 #include <stdarg.h>
27 
28 char xstr[1];		/* make loader happy */
29 short tfile = -1;	/* ditto */
30 
31 /*
32  *
33  * This program searches through the specified directory and then
34  * the directory _PATH_USRPRESERVE looking for an instance of the specified
35  * file from a crashed editor or a crashed system.
36  * If this file is found, it is unscrambled and written to
37  * the standard output.
38  *
39  * If this program terminates without a "broken pipe" diagnostic
40  * (i.e. the editor doesn't die right away) then the buffer we are
41  * writing from is removed when we finish.  This is potentially a mistake
42  * as there is not enough handshaking to guarantee that the file has actually
43  * been recovered, but should suffice for most cases.
44  */
45 
46 /*
47  * For lint's sake...
48  */
49 #ifndef lint
50 #define	ignorl(a)	a
51 #endif
52 
53 /*
54  * Limit on the number of printed entries
55  * when an, e.g. ``ex -r'' command is given.
56  */
57 #define	NENTRY	50
58 
59 char	*ctime();
60 char	nb[BUFSIZ];
61 int	vercnt;			/* Count number of versions of file found */
62 
63 main(argc, argv)
64 	int argc;
65 	char *argv[];
66 {
67 	register char *cp;
68 	register int b, i;
69 
70 	/*
71 	 * Initialize as though the editor had just started.
72 	 */
73 	fendcore = (line *) sbrk(0);
74 	dot = zero = dol = fendcore;
75 	one = zero + 1;
76 	endcore = fendcore - 2;
77 	iblock = oblock = -1;
78 
79 	/*
80 	 * If given only a -r argument, then list the saved files.
81 	 */
82 	if (argc == 2 && eq(argv[1], "-r")) {
83 		listfiles(_PATH_PRESERVE);
84 		exit(0);
85 	}
86 	if (argc != 3)
87 		error(" Wrong number of arguments to exrecover", 0);
88 
89 	CP(file, argv[2]);
90 
91 	/*
92 	 * Search for this file.
93 	 */
94 	findtmp(argv[1]);
95 
96 	/*
97 	 * Got (one of the versions of) it, write it back to the editor.
98 	 */
99 	cp = ctime(&H.Time);
100 	cp[19] = 0;
101 	fpr(" [Dated: %s", cp);
102 	fpr(vercnt > 1 ? ", newest of %d saved]" : "]", vercnt);
103 	H.Flines++;
104 
105 	/*
106 	 * Allocate space for the line pointers from the temp file.
107 	 */
108 	if ((int) sbrk((int) (H.Flines * sizeof (line))) == -1)
109 		/*
110 		 * Good grief.
111 		 */
112 		error(" Not enough core for lines", 0);
113 #ifdef DEBUG
114 	fpr("%d lines\n", H.Flines);
115 #endif
116 
117 	/*
118 	 * Now go get the blocks of seek pointers which are scattered
119 	 * throughout the temp file, reconstructing the incore
120 	 * line pointers at point of crash.
121 	 */
122 	b = 0;
123 	while (H.Flines > 0) {
124 		ignorl(lseek(tfile, (long) blocks[b] * BUFSIZ, 0));
125 		i = H.Flines < BUFSIZ / sizeof (line) ?
126 			H.Flines * sizeof (line) : BUFSIZ;
127 		if (read(tfile, (char *) dot, i) != i) {
128 			perror(nb);
129 			exit(1);
130 		}
131 		dot += i / sizeof (line);
132 		H.Flines -= i / sizeof (line);
133 		b++;
134 	}
135 	dot--; dol = dot;
136 
137 	/*
138 	 * Sigh... due to sandbagging some lines may really not be there.
139 	 * Find and discard such.  This shouldn't happen much.
140 	 */
141 	scrapbad();
142 
143 	/*
144 	 * Now if there were any lines in the recovered file
145 	 * write them to the standard output.
146 	 */
147 	if (dol > zero) {
148 		addr1 = one; addr2 = dol; io = 1;
149 		putfile(0);
150 	}
151 
152 	/*
153 	 * Trash the saved buffer.
154 	 * Hopefully the system won't crash before the editor
155 	 * syncs the new recovered buffer; i.e. for an instant here
156 	 * you may lose if the system crashes because this file
157 	 * is gone, but the editor hasn't completed reading the recovered
158 	 * file from the pipe from us to it.
159 	 *
160 	 * This doesn't work if we are coming from an non-absolute path
161 	 * name since we may have chdir'ed but what the hay, noone really
162 	 * ever edits with temporaries in "." anyways.
163 	 */
164 	if (nb[0] == '/')
165 		ignore(unlink(nb));
166 
167 	/*
168 	 * Adieu.
169 	 */
170 	exit(0);
171 }
172 
173 /*
174  * Print an error message (notably not in error
175  * message file).  If terminal is in RAW mode, then
176  * we should be writing output for "vi", so don't print
177  * a newline which would screw up the screen.
178  */
179 /*VARARGS2*/
180 error(str, inf)
181 	char *str;
182 	int inf;
183 {
184 
185 	fpr(str, inf);
186 	(void)ioctl(2, TIOCGETP, &tty);
187 	if ((tty.sg_flags & RAW) == 0)
188 		fpr("\n");
189 	exit(1);
190 }
191 
192 /*
193  * Here we save the information about files, when
194  * you ask us what files we have saved for you.
195  * We buffer file name, number of lines, and the time
196  * at which the file was saved.
197  */
198 struct svfile {
199 	char	sf_name[FNSIZE + 1];
200 	int	sf_lines;
201 	char	sf_entry[MAXNAMLEN + 1];
202 	time_t	sf_time;
203 };
204 
205 listfiles(dirname)
206 	char *dirname;
207 {
208 	register DIR *dir;
209 	struct direct *dirent;
210 	int ecount, qucmp();
211 	register int f;
212 	char *cp;
213 	struct svfile *fp, svbuf[NENTRY];
214 
215 	/*
216 	 * Open _PATH_PRESERVE, and go there to make things quick.
217 	 */
218 	dir = opendir(dirname);
219 	if (dir == NULL) {
220 		perror(dirname);
221 		return;
222 	}
223 	if (chdir(dirname) < 0) {
224 		perror(dirname);
225 		return;
226 	}
227 
228 	/*
229 	 * Look at the candidate files in _PATH_PRESERVE.
230 	 */
231 	fp = &svbuf[0];
232 	ecount = 0;
233 	while ((dirent = readdir(dir)) != NULL) {
234 		if (dirent->d_name[0] != 'E')
235 			continue;
236 #ifdef DEBUG
237 		fpr("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 			fpr("open failed\n");
249 #endif
250 			continue;
251 		}
252 		if (read(f, (char *) &H, sizeof H) != sizeof H) {
253 #ifdef DEBUG
254 			fpr("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 			fpr("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 		fpr("entered file %s\n", dirent->d_name);
274 #endif
275 	}
276 	ignore(closedir(dir));
277 
278 	/*
279 	 * If any files were saved, then sort them and print
280 	 * them out.
281 	 */
282 	if (ecount == 0) {
283 		fpr("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 		fpr("On %s at ", cp);
291  		cp[16] = 0;
292 		fpr(&cp[11]);
293 		fpr(" 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 _PATH_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 _PATH_PRESERVE and, if we can get there, /tmp
382 	 * (actually the users "directory" option).
383 	 */
384 	searchdir(dir);
385 	if (chdir(_PATH_PRESERVE) == 0)
386 		searchdir(_PATH_PRESERVE);
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 _PATH_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 DIR *dir;
428 	char dbuf[BUFSIZ];
429 
430 	dir = opendir(dirname);
431 	if (dir == NULL)
432 		return;
433 	while ((dirent = readdir(dir)) != NULL) {
434 		if (dirent->d_name[0] != 'E')
435 			continue;
436 		/*
437 		 * Got a file in the directory starting with E...
438 		 * Save a consed up name for the file to unlink
439 		 * later, and check that this is really a file
440 		 * we are looking for.
441 		 */
442 		ignore(strcat(strcat(strcpy(nb, dirname), "/"), dirent->d_name));
443 		if (yeah(nb)) {
444 			/*
445 			 * Well, it is the file we are looking for.
446 			 * Is it more recent than any version we found before?
447 			 */
448 			if (H.Time > besttime) {
449 				/*
450 				 * A winner.
451 				 */
452 				ignore(close(bestfd));
453 				bestfd = dup(tfile);
454 				besttime = H.Time;
455 				CP(bestnb, nb);
456 			}
457 			/*
458 			 * Count versions so user can be told there are
459 			 * ``yet more pages to be turned''.
460 			 */
461 			vercnt++;
462 		}
463 		ignore(close(tfile));
464 	}
465 	ignore(closedir(dir));
466 }
467 
468 /*
469  * Given a candidate file to be recovered, see
470  * if its really an editor temporary and of this
471  * user and the file specified.
472  */
473 yeah(name)
474 	char *name;
475 {
476 
477 	tfile = open(name, 2);
478 	if (tfile < 0)
479 		return (0);
480 	if (read(tfile, (char *) &H, sizeof H) != sizeof H) {
481 nope:
482 		ignore(close(tfile));
483 		return (0);
484 	}
485 	if (!eq(savedfile, file))
486 		goto nope;
487 	if (getuid() != H.Uid)
488 		goto nope;
489 	/*
490 	 * This is old and stupid code, which
491 	 * puts a word LOST in the header block, so that lost lines
492 	 * can be made to point at it.
493 	 */
494 	ignorl(lseek(tfile, (long)(BUFSIZ*HBLKS-8), 0));
495 	ignore(write(tfile, "LOST", 5));
496 	return (1);
497 }
498 
499 preserve()
500 {
501 
502 }
503 
504 /*
505  * Find the true end of the scratch file, and ``LOSE''
506  * lines which point into thin air.  This lossage occurs
507  * due to the sandbagging of i/o which can cause blocks to
508  * be written in a non-obvious order, different from the order
509  * in which the editor tried to write them.
510  *
511  * Lines which are lost are replaced with the text LOST so
512  * they are easy to find.  We work hard at pretty formatting here
513  * as lines tend to be lost in blocks.
514  *
515  * This only seems to happen on very heavily loaded systems, and
516  * not very often.
517  */
518 scrapbad()
519 {
520 	register line *ip;
521 	struct stat stbuf;
522 	off_t size, maxt;
523 	int bno, cnt, bad, was;
524 	char bk[BUFSIZ];
525 
526 	ignore(fstat(tfile, &stbuf));
527 	size = stbuf.st_size;
528 	maxt = (size >> SHFT) | (BNDRY-1);
529 	bno = (maxt >> OFFBTS) & BLKMSK;
530 #ifdef DEBUG
531 	fpr("size %ld, maxt %o, bno %d\n", size, maxt, bno);
532 #endif
533 
534 	/*
535 	 * Look for a null separating two lines in the temp file;
536 	 * if last line was split across blocks, then it is lost
537 	 * if the last block is.
538 	 */
539 	while (bno > 0) {
540 		ignorl(lseek(tfile, (long) BUFSIZ * bno, 0));
541 		cnt = read(tfile, (char *) bk, BUFSIZ);
542 		while (cnt > 0)
543 			if (bk[--cnt] == 0)
544 				goto null;
545 		bno--;
546 	}
547 null:
548 
549 	/*
550 	 * Magically calculate the largest valid pointer in the temp file,
551 	 * consing it up from the block number and the count.
552 	 */
553 	maxt = ((bno << OFFBTS) | (cnt >> SHFT)) & ~1;
554 #ifdef DEBUG
555 	fpr("bno %d, cnt %d, maxt %o\n", bno, cnt, maxt);
556 #endif
557 
558 	/*
559 	 * Now cycle through the line pointers,
560 	 * trashing the Lusers.
561 	 */
562 	was = bad = 0;
563 	for (ip = one; ip <= dol; ip++)
564 		if (*ip > maxt) {
565 #ifdef DEBUG
566 			fpr("%d bad, %o > %o\n", ip - zero, *ip, maxt);
567 #endif
568 			if (was == 0)
569 				was = ip - zero;
570 			*ip = ((HBLKS*BUFSIZ)-8) >> SHFT;
571 		} else if (was) {
572 			if (bad == 0)
573 				fpr(" [Lost line(s):");
574 			fpr(" %d", was);
575 			if ((ip - 1) - zero > was)
576 				fpr("-%d", (ip - 1) - zero);
577 			bad++;
578 			was = 0;
579 		}
580 	if (was != 0) {
581 		if (bad == 0)
582 			fpr(" [Lost line(s):");
583 		fpr(" %d", was);
584 		if (dol - zero != was)
585 			fpr("-%d", dol - zero);
586 		bad++;
587 	}
588 	if (bad)
589 		fpr("]");
590 }
591 
592 /*
593  * Aw shucks, if we only had a (void) cast.
594  */
595 #ifdef lint
596 Ignorl(a)
597 	long a;
598 {
599 
600 	a = a;
601 }
602 
603 Ignore(a)
604 	char *a;
605 {
606 
607 	a = a;
608 }
609 
610 Ignorf(a)
611 	int (*a)();
612 {
613 
614 	a = a;
615 }
616 
617 ignorl(a)
618 	long a;
619 {
620 
621 	a = a;
622 }
623 #endif
624 
625 int	cntch, cntln, cntodd, cntnull;
626 /*
627  * Following routines stolen mercilessly from ex.
628  */
629 putfile()
630 {
631 	line *a1;
632 	register char *fp, *lp;
633 	register int nib;
634 
635 	a1 = addr1;
636 	clrstats();
637 	cntln = addr2 - a1 + 1;
638 	if (cntln == 0)
639 		return;
640 	nib = BUFSIZ;
641 	fp = genbuf;
642 	do {
643 		getline(*a1++);
644 		lp = linebuf;
645 		for (;;) {
646 			if (--nib < 0) {
647 				nib = fp - genbuf;
648 				if (write(io, genbuf, nib) != nib)
649 					wrerror();
650 				cntch += nib;
651 				nib = 511;
652 				fp = genbuf;
653 			}
654 			if ((*fp++ = *lp++) == 0) {
655 				fp[-1] = '\n';
656 				break;
657 			}
658 		}
659 	} while (a1 <= addr2);
660 	nib = fp - genbuf;
661 	if (write(io, genbuf, nib) != nib)
662 		wrerror();
663 	cntch += nib;
664 }
665 
666 wrerror()
667 {
668 
669 	syserror();
670 }
671 
672 clrstats()
673 {
674 
675 	ninbuf = 0;
676 	cntch = 0;
677 	cntln = 0;
678 	cntnull = 0;
679 	cntodd = 0;
680 }
681 
682 #define	READ	0
683 #define	WRITE	1
684 
685 getline(tl)
686 	line tl;
687 {
688 	register char *bp, *lp;
689 	register int nl;
690 
691 	lp = linebuf;
692 	bp = getblock(tl, READ);
693 	nl = nleft;
694 	tl &= ~OFFMSK;
695 	while (*lp++ = *bp++)
696 		if (--nl == 0) {
697 			bp = getblock(tl += INCRMT, READ);
698 			nl = nleft;
699 		}
700 }
701 
702 int	read();
703 int	write();
704 
705 char *
706 getblock(atl, iof)
707 	line atl;
708 	int iof;
709 {
710 	register int bno, off;
711 
712 	bno = (atl >> OFFBTS) & BLKMSK;
713 	off = (atl << SHFT) & LBTMSK;
714 	if (bno >= NMBLKS)
715 		error(" Tmp file too large");
716 	nleft = BUFSIZ - off;
717 	if (bno == iblock) {
718 		ichanged |= iof;
719 		return (ibuff + off);
720 	}
721 	if (bno == oblock)
722 		return (obuff + off);
723 	if (iof == READ) {
724 		if (ichanged)
725 			blkio(iblock, ibuff, write);
726 		ichanged = 0;
727 		iblock = bno;
728 		blkio(bno, ibuff, read);
729 		return (ibuff + off);
730 	}
731 	if (oblock >= 0)
732 		blkio(oblock, obuff, write);
733 	oblock = bno;
734 	return (obuff + off);
735 }
736 
737 blkio(b, buf, iofcn)
738 	short b;
739 	char *buf;
740 	int (*iofcn)();
741 {
742 
743 	lseek(tfile, (long) (unsigned) b * BUFSIZ, 0);
744 	if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ)
745 		syserror();
746 }
747 
748 syserror()
749 {
750 	char *strerror();
751 
752 	dirtcnt = 0;
753 	write(2, " ", 1);
754 	error(strerror(errno));
755 	exit(1);
756 }
757 
758 /*
759  * Must avoid stdio because expreserve uses sbrk to do memory
760  * allocation and stdio uses malloc.
761  */
762 fpr(fmt)
763 	char *fmt;
764 {
765 	va_list ap;
766 	int len;
767 	char buf[BUFSIZ];
768 
769 	va_start(ap, fmt);
770 	len = vsnprintf(buf, sizeof(buf), fmt, ap);
771 	va_end(ap);
772 	write(2, buf, len);
773 }
774