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