release 3.3, Feb 2, 1980
[unix-history] / usr / src / usr.bin / ex / ex3.7recover / ex3.7recover.c
CommitLineData
f3270895
MH
1/* Copyright (c) 1979 Regents of the University of California */
2#include "ex.h"
3#include "ex_temp.h"
4#include "ex_tty.h"
5#include "local/uparm.h"
6
7#undef BUFSIZ
8#undef EOF
9#undef NULL
10
11#include <stdio.h>
12#include <sys/dir.h>
13
14/*
15 * Ex recovery program
16 * exrecover dir name
17 * exrecover -r
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 */
43char 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
51char *ctime();
52char nb[BUFSIZ];
53int vercnt; /* Count number of versions of file found */
54
55main(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();
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*/
172error(str, inf)
173 char *str;
174 int inf;
175{
176
177 fprintf(stderr, str, inf);
178 gtty(2, &tty);
179 if ((tty.sg_flags & RAW) == 0)
180 fprintf(stderr, "\n");
181 exit(1);
182}
183
184/*
185 * Here we save the information about files, when
186 * you ask us what files we have saved for you.
187 * We buffer file name, number of lines, and the time
188 * at which the file was saved.
189 */
190struct svfile {
191 char sf_name[FNSIZE + 1];
192 int sf_lines;
193 char sf_entry[DIRSIZ + 1];
194 time_t sf_time;
195};
196
197listfiles(dirname)
198 char *dirname;
199{
200 register FILE *dir;
201 struct direct dirent;
202 int ecount, qucmp();
203 register int f;
204 char *cp;
205 struct svfile *fp, svbuf[NENTRY];
206
207 /*
208 * Open usrpath(preserve), and go there to make things quick.
209 */
210 dir = fopen(dirname, "r");
211 if (dir == NULL) {
212 perror(dirname);
213 return;
214 }
215 if (chdir(dirname) < 0) {
216 perror(dirname);
217 return;
218 }
219
220 /*
221 * Look at the candidate files in usrpath(preserve).
222 */
223 fp = &svbuf[0];
224 ecount = 0;
225 while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) {
226 if (dirent.d_ino == 0)
227 continue;
228 if (dirent.d_name[0] != 'E')
229 continue;
230#ifdef DEBUG
231 fprintf(stderr, "considering %s\n", dirent.d_name);
232#endif
233 /*
234 * Name begins with E; open it and
235 * make sure the uid in the header is our uid.
236 * If not, then don't bother with this file, it can't
237 * be ours.
238 */
239 f = open(dirent.d_name, 0);
240 if (f < 0) {
241#ifdef DEBUG
242 fprintf(stderr, "open failed\n");
243#endif
244 continue;
245 }
246 if (read(f, (char *) &H, sizeof H) != sizeof H) {
247#ifdef DEBUG
248 fprintf(stderr, "culdnt read hedr\n");
249#endif
250 ignore(close(f));
251 continue;
252 }
253 ignore(close(f));
254 if (getuid() != H.Uid) {
255#ifdef DEBUG
256 fprintf(stderr, "uid wrong\n");
257#endif
258 continue;
259 }
260
261 /*
262 * Saved the day!
263 */
264 enter(fp++, dirent.d_name, ecount);
265 ecount++;
266#ifdef DEBUG
267 fprintf(stderr, "entered file %s\n", dirent.d_name);
268#endif
269 }
270 ignore(fclose(dir));
271
272 /*
273 * If any files were saved, then sort them and print
274 * them out.
275 */
276 if (ecount == 0) {
277 fprintf(stderr, "No files saved.\n");
278 return;
279 }
280 qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp);
281 for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) {
282 cp = ctime(&fp->sf_time);
283 cp[10] = 0;
284 fprintf(stderr, "On %s at ", cp);
285 cp[16] = 0;
286 fprintf(stderr, &cp[11]);
287 fprintf(stderr, " saved %d lines of file \"%s\"\n",
288 fp->sf_lines, fp->sf_name);
289 }
290}
291
292/*
293 * Enter a new file into the saved file information.
294 */
295enter(fp, fname, count)
296 struct svfile *fp;
297 char *fname;
298{
299 register char *cp, *cp2;
300 register struct svfile *f, *fl;
301 time_t curtime, itol();
302
303 f = 0;
304 if (count >= NENTRY) {
305 /*
306 * My god, a huge number of saved files.
307 * Would you work on a system that crashed this
308 * often? Hope not. So lets trash the oldest
309 * as the most useless.
310 *
311 * (I wonder if this code has ever run?)
312 */
313 fl = fp - count + NENTRY - 1;
314 curtime = fl->sf_time;
315 for (f = fl; --f > fp-count; )
316 if (f->sf_time < curtime)
317 curtime = f->sf_time;
318 for (f = fl; --f > fp-count; )
319 if (f->sf_time == curtime)
320 break;
321 fp = f;
322 }
323
324 /*
325 * Gotcha.
326 */
327 fp->sf_time = H.Time;
328 fp->sf_lines = H.Flines;
329 for (cp2 = fp->sf_name, cp = savedfile; *cp;)
330 *cp2++ = *cp++;
331 for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;)
332 *cp2++ = *cp++;
333 *cp2++ = 0;
334}
335
336/*
337 * Do the qsort compare to sort the entries first by file name,
338 * then by modify time.
339 */
340qucmp(p1, p2)
341 struct svfile *p1, *p2;
342{
343 register int t;
344
345 if (t = strcmp(p1->sf_name, p2->sf_name))
346 return(t);
347 if (p1->sf_time > p2->sf_time)
348 return(-1);
349 return(p1->sf_time < p2->sf_time);
350}
351
352/*
353 * Scratch for search.
354 */
355char bestnb[BUFSIZ]; /* Name of the best one */
356long besttime; /* Time at which the best file was saved */
357int bestfd; /* Keep best file open so it dont vanish */
358
359/*
360 * Look for a file, both in the users directory option value
361 * (i.e. usually /tmp) and in usrpath(preserve).
362 * Want to find the newest so we search on and on.
363 */
364findtmp(dir)
365 char *dir;
366{
367
368 /*
369 * No name or file so far.
370 */
371 bestnb[0] = 0;
372 bestfd = -1;
373
374 /*
375 * Search usrpath(preserve) and, if we can get there, /tmp
376 * (actually the users "directory" option).
377 */
378 searchdir(dir);
379 if (chdir(mydir) == 0)
380 searchdir(mydir);
381 if (bestfd != -1) {
382 /*
383 * Gotcha.
384 * Put the file (which is already open) in the file
385 * used by the temp file routines, and save its
386 * name for later unlinking.
387 */
388 tfile = bestfd;
389 CP(nb, bestnb);
390 ignorl(lseek(tfile, 0l, 0));
391
392 /*
393 * Gotta be able to read the header or fall through
394 * to lossage.
395 */
396 if (read(tfile, (char *) &H, sizeof H) == sizeof H)
397 return;
398 }
399
400 /*
401 * Extreme lossage...
402 */
403 error(" File not found", 0);
404}
405
406/*
407 * Search for the file in directory dirname.
408 *
409 * Don't chdir here, because the users directory
410 * may be ".", and we would move away before we searched it.
411 * Note that we actually chdir elsewhere (because it is too slow
412 * to look around in usrpath(preserve) without chdir'ing there) so we
413 * can't win, because we don't know the name of '.' and if the path
414 * name of the file we want to unlink is relative, rather than absolute
415 * we won't be able to find it again.
416 */
417searchdir(dirname)
418 char *dirname;
419{
420 struct direct dirent;
421 register FILE *dir;
422 char dbuf[BUFSIZ];
423
424 dir = fopen(dirname, "r");
425 if (dir == NULL)
426 return;
427 setbuf(dir, dbuf);
428 while (fread((char *) &dirent, sizeof dirent, 1, dir) == 1) {
429 if (dirent.d_ino == 0)
430 continue;
431 if (dirent.d_name[0] != 'E' || dirent.d_name[DIRSIZ - 1] != 0)
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(fclose(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 */
470yeah(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) {
478nope:
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 */
44232d5b 491 ignorl(lseek(tfile, (long)(BUFSIZ*HBLKS-8), 0));
f3270895
MH
492 ignore(write(tfile, "LOST", 5));
493 return (1);
494}
495
496preserve()
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 */
515scrapbad()
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;
44232d5b 525 maxt = (size >> SHFT) | (BNDRY-1);
f3270895
MH
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 }
544null:
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;
44232d5b 567 *ip = ((HBLKS*BUFSIZ)-8) >> SHFT;
f3270895
MH
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
593Ignorl(a)
594 long a;
595{
596
597 a = a;
598}
599
600Ignore(a)
601 char *a;
602{
603
604 a = a;
605}
606
607Ignorf(a)
608 int (*a)();
609{
610
611 a = a;
612}
613
614ignorl(a)
615 long a;
616{
617
618 a = a;
619}
620#endif
621
622int cntch, cntln, cntodd, cntnull;
623/*
624 * Following routines stolen mercilessly from ex.
625 */
626putfile()
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
663wrerror()
664{
665
666 syserror();
667}
668
669clrstats()
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
682getline(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
699int read();
700int write();
701
702char *
703getblock(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
734blkio(b, buf, iofcn)
735 short b;
736 char *buf;
737 int (*iofcn)();
738{
739
740 lseek(tfile, (long) (unsigned) b * BUFSIZ, 0);
44232d5b 741 if ((*iofcn)(tfile, buf, BUFSIZ) != BUFSIZ)
f3270895
MH
742 syserror();
743}
744
745syserror()
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}