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
main(argc,argv)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*/
error(str,inf)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
listfiles(dirname)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 */
findtmp(dir)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 */
searchdir(dirname)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 */
yeah(name)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
preserve()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 */
scrapbad()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
Ignorl(a)607 Ignorl(a)
608 long a;
609 {
610
611 a = a;
612 }
613
Ignore(a)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
ignorl(a)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 */
putfile()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
wrerror()677 wrerror()
678 {
679
680 syserror();
681 }
682
clrstats()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
getline(tl)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 *
getblock(atl,iof)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
blkio(b,buf,iofcn)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
syserror()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__
fpr(const char * fmt,...)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