/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* Copyright (c) 1981 Regents of the University of California */ #include /* BUFSIZ: stdio = 1024, VMUNIX = 1024 */ #ifndef TRACE #undef NULL #endif #include "ex.h" #include "ex_temp.h" #include "ex_tty.h" #include "ex_tune.h" #include #include #include #include #include #define DIRSIZ MAXNAMLEN short tfile = -1; /* ditto */ /* * * This program searches through the specified directory and then * the directory usrpath(preserve) looking for an instance of the specified * file from a crashed editor or a crashed system. * If this file is found, it is unscrambled and written to * the standard output. * * If this program terminates without a "broken pipe" diagnostic * (i.e. the editor doesn't die right away) then the buffer we are * writing from is removed when we finish. This is potentially a mistake * as there is not enough handshaking to guarantee that the file has actually * been recovered, but should suffice for most cases. */ /* * This directory definition also appears (obviously) in expreserve.c. * Change both if you change either. */ unsigned char mydir[PATH_MAX+1]; /* * Limit on the number of printed entries * when an, e.g. ``ex -r'' command is given. */ #define NENTRY 50 unsigned char nb[BUFSIZE]; int vercnt; /* Count number of versions of file found */ void rputfile(void); void rsyserror(void); void searchdir(unsigned char *); void scrapbad(void); void findtmp(unsigned char *); void listfiles(unsigned char *); int main(int argc, char *argv[]) { unsigned char string[50]; unsigned char *cp; int c, b, i; int rflg = 0, errflg = 0; int label; line *tmpadr; extern unsigned char *mypass(); struct passwd *pp = getpwuid(getuid()); unsigned char rmcmd[PATH_MAX+1]; (void)setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void)textdomain(TEXT_DOMAIN); cp = string; strcpy(mydir, USRPRESERVE); if (pp == NULL) { fprintf(stderr, gettext("Unable to get user's id\n")); exit(-1); } strcat(mydir, pp->pw_name); /* * Initialize as though the editor had just started. */ fendcore = (line *) sbrk(0); dot = zero = dol = fendcore; one = zero + 1; endcore = fendcore - 2; iblock = oblock = -1; while ((c=getopt(argc, (char **)argv, "rx")) != EOF) switch (c) { case 'r': rflg++; break; case 'x': xflag++; break; case '?': errflg++; break; } argc -= optind; argv = &argv[optind]; if (errflg) exit(2); /* * If given only a -r argument, then list the saved files. * (NOTE: single -r argument is scheduled to be replaced by -L). */ if (rflg && argc == 0) { fprintf(stderr,"%s:\n", mydir); listfiles(mydir); fprintf(stderr,"%s:\n", TMPDIR); listfiles((unsigned char *)TMPDIR); exit(0); } if (argc != 2) error(gettext(" Wrong number of arguments to exrecover"), 0); CP(file, argv[1]); /* * Search for this file. */ findtmp((unsigned char *)argv[0]); /* * Got (one of the versions of) it, write it back to the editor. */ (void)cftime((char *)cp, "%a %h %d %T", &H.Time); fprintf(stderr, vercnt > 1 ? gettext(" [Dated: %s, newest of %d saved]") : gettext(" [Dated: %s]"), cp, vercnt); fprintf(stderr, "\r\n"); if(H.encrypted) { if(xflag) { kflag = run_setkey(perm, (unsigned char *)getenv("CrYpTkEy")); } else kflag = run_setkey(perm, mypass("Enter key:")); if(kflag == -1) { kflag = 0; xflag = 0; fprintf(stderr,gettext("Encryption facility not available\n")); exit(-1); } xtflag = 1; if (makekey(tperm) != 0) { xtflag = 0; fprintf(stderr,gettext("Warning--Cannot encrypt temporary buffer\n")); exit(-1); } } fprintf(stderr,gettext("\r\n [Hit return to continue]")); fflush(stderr); setbuf(stdin, (char *)NULL); while((c = getchar()) != '\n' && c != '\r'); H.Flines++; /* * Allocate space for the line pointers from the temp file. */ if ((int) sbrk((int) (H.Flines * sizeof (line))) == -1) error(gettext(" Not enough core for lines"), 0); #ifdef DEBUG fprintf(stderr, "%d lines\n", H.Flines); #endif /* * Now go get the blocks of seek pointers which are scattered * throughout the temp file, reconstructing the incore * line pointers at point of crash. */ b = 0; while (H.Flines > 0) { (void)lseek(tfile, (long) blocks[b] * BUFSIZE, 0); i = H.Flines < BUFSIZE / sizeof (line) ? H.Flines * sizeof (line) : BUFSIZE; if (read(tfile, (char *) dot, i) != i) { perror((char *)nb); exit(1); } dot += i / sizeof (line); H.Flines -= i / sizeof (line); b++; } dot--; dol = dot; /* * Due to sandbagging some lines may really not be there. * Find and discard such. This shouldn't happen often. */ scrapbad(); /* * Now if there were any lines in the recovered file * write them to the standard output. */ if (dol > zero) { addr1 = one; addr2 = dol; io = 1; rputfile(); } /* * Trash the saved buffer. * Hopefully the system won't crash before the editor * syncs the new recovered buffer; i.e. for an instant here * you may lose if the system crashes because this file * is gone, but the editor hasn't completed reading the recovered * file from the pipe from us to it. * * This doesn't work if we are coming from an non-absolute path * name since we may have chdir'ed but what the hay, noone really * ever edits with temporaries in "." anyways. */ if (nb[0] == '/') { (void)unlink((const char *)nb); sprintf((char *)rmcmd, "rmdir %s 2> /dev/null", (char *)mydir); system((char *)rmcmd); } return (0); } /* * Print an error message (notably not in error * message file). If terminal is in RAW mode, then * we should be writing output for "vi", so don't print * a newline which would mess up the screen. */ /*VARARGS2*/ void error(str, inf) unsigned char *str; int inf; { struct termio termio; if (inf) fprintf(stderr, (char *)str, inf); else fprintf(stderr, (char *)str); ioctl(2, TCGETA, &termio); if (termio.c_lflag & ICANON) fprintf(stderr, "\n"); exit(1); } /* * Here we save the information about files, when * you ask us what files we have saved for you. * We buffer file name, number of lines, and the time * at which the file was saved. */ struct svfile { unsigned char sf_name[FNSIZE + 1]; int sf_lines; unsigned char sf_entry[DIRSIZ + 1]; time_t sf_time; short sf_encrypted; }; void enter(struct svfile *, unsigned char *, int); void listfiles(unsigned char *dirname) { DIR *dir; struct dirent64 *direntry; int ecount, qucmp(); int f; unsigned char cp[50]; unsigned char cp2[50]; unsigned char *filname; struct svfile *fp, svbuf[NENTRY]; /* * Open usrpath(preserve), and go there to make things quick. */ if ((dir = opendir((char *)dirname)) == NULL) { fprintf(stderr,gettext("No files saved.\n")); return; } if (chdir((const char *)dirname) < 0) { perror((char *)dirname); return; } /* * Look at the candidate files in usrpath(preserve). */ fp = &svbuf[0]; ecount = 0; while ((direntry = readdir64(dir)) != NULL) { filname = (unsigned char *)direntry->d_name; if (filname[0] != 'E') continue; #ifdef DEBUG fprintf(stderr, "considering %s\n", filname); #endif /* * Name begins with E; open it and * make sure the uid in the header is our uid. * If not, then don't bother with this file, it can't * be ours. */ f = open(filname, 0); if (f < 0) { #ifdef DEBUG fprintf(stderr, "open failed\n"); #endif continue; } if (read(f, (char *) &H, sizeof H) != sizeof H) { #ifdef DEBUG fprintf(stderr, "could not read header\n"); #endif (void)close(f); continue; } (void)close(f); if (getuid() != H.Uid) { #ifdef DEBUG fprintf(stderr, "uid wrong\n"); #endif continue; } /* * Saved the day! */ enter(fp++, filname, ecount); ecount++; #ifdef DEBUG fprintf(stderr, "entered file %s\n", filname); #endif } (void)closedir(dir); /* * If any files were saved, then sort them and print * them out. */ if (ecount == 0) { fprintf(stderr, gettext("No files saved.\n")); return; } qsort(&svbuf[0], ecount, sizeof svbuf[0], qucmp); for (fp = &svbuf[0]; fp < &svbuf[ecount]; fp++) { (void)cftime((char *)cp, "%a %b %d", &fp->sf_time); (void)cftime((char *)cp2, "%R", &fp->sf_time); fprintf(stderr, gettext("On %s at %s, saved %d lines of file \"%s\" "), cp, cp2, fp->sf_lines, fp->sf_name); fprintf(stderr, "%s\n", (fp->sf_encrypted) ? gettext("[ENCRYPTED]") : ""); } } /* * Enter a new file into the saved file information. */ void enter(struct svfile *fp, unsigned char *fname, int count) { unsigned char *cp, *cp2; struct svfile *f, *fl; time_t curtime; f = 0; if (count >= NENTRY) { /* * Trash the oldest as the most useless. */ fl = fp - count + NENTRY - 1; curtime = fl->sf_time; for (f = fl; --f > fp-count; ) if (f->sf_time < curtime) curtime = f->sf_time; for (f = fl; --f > fp-count; ) if (f->sf_time == curtime) break; fp = f; } /* * Gotcha. */ fp->sf_time = H.Time; fp->sf_lines = H.Flines; fp->sf_encrypted = H.encrypted; for (cp2 = fp->sf_name, cp = savedfile; *cp;) *cp2++ = *cp++; *cp2++ = 0; for (cp2 = fp->sf_entry, cp = fname; *cp && cp-fname < 14;) *cp2++ = *cp++; *cp2++ = 0; } /* * Do the qsort compare to sort the entries first by file name, * then by modify time. */ int qucmp(struct svfile *p1, struct svfile *p2) { int t; if (t = strcmp(p1->sf_name, p2->sf_name)) return(t); if (p1->sf_time > p2->sf_time) return(-1); return(p1->sf_time < p2->sf_time); } /* * Scratch for search. */ unsigned char bestnb[BUFSIZE]; /* Name of the best one */ long besttime = 0; /* Time at which the best file was saved */ int bestfd; /* Keep best file open so it dont vanish */ /* * Look for a file, both in the users directory option value * (i.e. usually /tmp) and in usrpath(preserve). * Want to find the newest so we search on and on. */ void findtmp(unsigned char *dir) { /* * No name or file so far. */ bestnb[0] = 0; bestfd = -1; /* * Search usrpath(preserve) and, if we can get there, /tmp * (actually the user's "directory" option). */ searchdir(dir); if (chdir((const char *)mydir) == 0) searchdir(mydir); if (bestfd != -1) { /* * Gotcha. * Put the file (which is already open) in the file * used by the temp file routines, and save its * name for later unlinking. */ tfile = bestfd; CP(nb, bestnb); (void)lseek(tfile, 0l, 0); /* * Gotta be able to read the header or fall through * to lossage. */ if (read(tfile, (char *) &H, sizeof H) == sizeof H) return; } /* * Extreme lossage... */ error((unsigned char *)gettext(" File not found"), 0); } /* * Search for the file in directory dirname. * * Don't chdir here, because the users directory * may be ".", and we would move away before we searched it. * Note that we actually chdir elsewhere (because it is too slow * to look around in usrpath(preserve) without chdir'ing there) so we * can't win, because we don't know the name of '.' and if the path * name of the file we want to unlink is relative, rather than absolute * we won't be able to find it again. */ void searchdir(unsigned char *dirname) { struct dirent64 *direntry; DIR *dir; unsigned char dbuf[BUFSIZE]; unsigned char *filname; if ((dir = opendir((char *)dirname)) == NULL) return; while ((direntry = readdir64(dir)) != NULL) { filname = (unsigned char *)direntry->d_name; if (filname[0] != 'E' || filname[1] != 'x') continue; /* * Got a file in the directory starting with Ex... * Save a consed up name for the file to unlink * later, and check that this is really a file * we are looking for. */ (void)strcat(strcat(strcpy(nb, dirname), "/"), filname); if (yeah(nb)) { /* * Well, it is the file we are looking for. * Is it more recent than any version we found before? */ if (H.Time > besttime) { /* * A winner. */ (void)close(bestfd); bestfd = dup(tfile); besttime = H.Time; CP(bestnb, nb); } /* * Count versions and tell user */ vercnt++; } (void)close(tfile); } (void)closedir(dir); } /* * Given a candidate file to be recovered, see * if it's really an editor temporary and of this * user and the file specified. */ int yeah(unsigned char *name) { tfile = open(name, 2); if (tfile < 0) return (0); if (read(tfile, (char *) &H, sizeof H) != sizeof H) { nope: (void)close(tfile); return (0); } if (!eq(savedfile, file)) goto nope; if (getuid() != H.Uid) goto nope; /* * Old code: puts a word LOST in the header block, so that lost lines * can be made to point at it. */ (void)lseek(tfile, (long)(BUFSIZE*HBLKS-8), 0); (void)write(tfile, "LOST", 5); return (1); } /* * Find the true end of the scratch file, and ``LOSE'' * lines which point into thin air. This lossage occurs * due to the sandbagging of i/o which can cause blocks to * be written in a non-obvious order, different from the order * in which the editor tried to write them. * * Lines which are lost are replaced with the text LOST so * they are easy to find. We work hard at pretty formatting here * as lines tend to be lost in blocks. * * This only seems to happen on very heavily loaded systems, and * not very often. */ void scrapbad(void) { line *ip; struct stat64 stbuf; off_t size, maxt; int bno, cnt, bad, was; unsigned char bk[BUFSIZE]; (void)fstat64(tfile, &stbuf); size = (off_t)stbuf.st_size; maxt = (size >> SHFT) | (BNDRY-1); bno = (maxt >> OFFBTS) & BLKMSK; #ifdef DEBUG fprintf(stderr, "size %ld, maxt %o, bno %d\n", size, maxt, bno); #endif /* * Look for a null separating two lines in the temp file; * if last line was split across blocks, then it is lost * if the last block is. */ while (bno > 0) { (void)lseek(tfile, (long) BUFSIZE * bno, 0); cnt = read(tfile, (char *) bk, BUFSIZE); if(xtflag) if (run_crypt(0L, bk, CRSIZE, tperm) == -1) rsyserror(); #ifdef DEBUG fprintf(stderr,"UNENCRYPTED: BLK %d\n",bno); #endif while (cnt > 0) if (bk[--cnt] == 0) goto null; bno--; } null: /* * Magically calculate the largest valid pointer in the temp file, * consing it up from the block number and the count. */ maxt = ((bno << OFFBTS) | (cnt >> SHFT)) & ~1; #ifdef DEBUG fprintf(stderr, "bno %d, cnt %d, maxt %o\n", bno, cnt, maxt); #endif /* * Now cycle through the line pointers, * trashing the Lusers. */ was = bad = 0; for (ip = one; ip <= dol; ip++) if (*ip > maxt) { #ifdef DEBUG fprintf(stderr, "%d bad, %o > %o\n", ip - zero, *ip, maxt); #endif if (was == 0) was = ip - zero; *ip = ((HBLKS*BUFSIZE)-8) >> SHFT; } else if (was) { if (bad == 0) fprintf(stderr, gettext(" [Lost line(s):")); fprintf(stderr, " %d", was); if ((ip - 1) - zero > was) fprintf(stderr, "-%d", (ip - 1) - zero); bad++; was = 0; } if (was != 0) { if (bad == 0) fprintf(stderr, " [Lost line(s):"); fprintf(stderr, " %d", was); if (dol - zero != was) fprintf(stderr, "-%d", dol - zero); bad++; } if (bad) fprintf(stderr, "]"); } int cntch, cntln, cntodd, cntnull; /* * Following routines stolen mercilessly from ex. */ void rputfile(void) { line *a1; unsigned char *fp, *lp; int nib; a1 = addr1; clrstats(); cntln = addr2 - a1 + 1; if (cntln == 0) return; nib = BUFSIZE; fp = genbuf; do { #ifdef DEBUG fprintf(stderr,"GETTING A LINE \n"); #endif getaline(*a1++); lp = linebuf; #ifdef DEBUG fprintf(stderr,"LINE:%s\n",linebuf); #endif for (;;) { if (--nib < 0) { nib = fp - genbuf; if (write(io, genbuf, nib) != nib) wrerror(); cntch += nib; nib = BUFSIZE; fp = genbuf; } if ((*fp++ = *lp++) == 0) { fp[-1] = '\n'; break; } } } while (a1 <= addr2); nib = fp - genbuf; if (write(io, genbuf, nib) != nib) wrerror(); cntch += nib; } void wrerror(void) { rsyserror(); } void clrstats(void) { ninbuf = 0; cntch = 0; cntln = 0; cntnull = 0; cntodd = 0; } #define READ 0 #define WRITE 1 void getaline(line tl) { unsigned char *bp, *lp; int nl; lp = linebuf; bp = getblock(tl); nl = nleft; tl &= ~OFFMSK; while (*lp++ = *bp++) if (--nl == 0) { bp = getblock(tl += INCRMT); nl = nleft; } } int read(); int write(); unsigned char * getblock(atl) line atl; { int bno, off; unsigned char *p1, *p2; int n; bno = (atl >> OFFBTS) & BLKMSK; #ifdef DEBUG fprintf(stderr,"GETBLOCK: BLK %d\n",bno); #endif off = (atl << SHFT) & LBTMSK; if (bno >= NMBLKS) error((unsigned char *)gettext(" Tmp file too large")); nleft = BUFSIZE - off; if (bno == iblock) return (ibuff + off); iblock = bno; blkio(bno, ibuff, read); if(xtflag) if (run_crypt(0L, ibuff, CRSIZE, tperm) == -1) rsyserror(); #ifdef DEBUG fprintf(stderr,"UNENCRYPTED: BLK %d\n",bno); #endif return (ibuff + off); } void blkio(short b, unsigned char *buf, int (*iofcn)()) { int rc; lseek(tfile, (long) (unsigned) b * BUFSIZE, 0); if ((rc =(*iofcn)(tfile, buf, BUFSIZE)) != BUFSIZE) { (void)fprintf(stderr,gettext("Failed on BLK: %d with %d/%d\n"),b,rc,BUFSIZE); perror(""); rsyserror(); } } void rsyserror(void) { int save_err = errno; dirtcnt = 0; write(2, " ", 1); error(strerror(save_err)); exit(1); } static int intrupt; static void catch(); unsigned char * mypass(prompt) unsigned char *prompt; { struct termio ttyb; unsigned short flags; unsigned char *p; int c; static unsigned char pbuf[9]; void (*sig)(); setbuf(stdin, (char*)NULL); sig = signal(SIGINT, catch); intrupt = 0; (void) ioctl(fileno(stdin), TCGETA, &ttyb); flags = ttyb.c_lflag; ttyb.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); (void) ioctl(fileno(stdin), TCSETAF, &ttyb); (void) fputs((char *)prompt, stderr); for(p=pbuf; !intrupt && (c = getc(stdin)) != '\n' && c!= '\r' && c != EOF; ) { if(p < &pbuf[8]) *p++ = c; } *p = '\0'; (void) putc('\n', stderr); ttyb.c_lflag = flags; (void) ioctl(fileno(stdin), TCSETA, &ttyb); (void) signal(SIGINT, sig); if(intrupt) (void) kill(getpid(), SIGINT); return(pbuf); } static void catch() { ++intrupt; }