/*
* 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 <stdio.h> /* 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 <pwd.h>
#include <locale.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#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;
}