1 /*
2  * Copyright (c) 1985 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  */
6 
7 #ifndef lint
8 static char sccsid[] = "@(#)interactive.c	5.5 (Berkeley) 4/23/87";
9 #endif not lint
10 
11 #include "restore.h"
12 #include <protocols/dumprestore.h>
13 #include <setjmp.h>
14 
15 #define round(a, b) (((a) + (b) - 1) / (b) * (b))
16 
17 /*
18  * Things to handle interruptions.
19  */
20 static jmp_buf reset;
21 static char *nextarg = NULL;
22 
23 /*
24  * Structure and routines associated with listing directories.
25  */
26 struct afile {
27 	ino_t	fnum;		/* inode number of file */
28 	char	*fname;		/* file name */
29 	short	fflags;		/* extraction flags, if any */
30 	char	ftype;		/* file type, e.g. LEAF or NODE */
31 };
32 struct arglist {
33 	struct afile	*head;	/* start of argument list */
34 	struct afile	*last;	/* end of argument list */
35 	struct afile	*base;	/* current list arena */
36 	int		nent;	/* maximum size of list */
37 	char		*cmd;	/* the current command */
38 };
39 extern int fcmp();
40 extern char *fmtentry();
41 char *copynext();
42 
43 /*
44  * Read and execute commands from the terminal.
45  */
46 runcmdshell()
47 {
48 	register struct entry *np;
49 	ino_t ino;
50 	static struct arglist alist = { 0, 0, 0, 0, 0 };
51 	char curdir[MAXPATHLEN];
52 	char name[MAXPATHLEN];
53 	char cmd[BUFSIZ];
54 
55 	canon("/", curdir);
56 loop:
57 	if (setjmp(reset) != 0) {
58 		for (; alist.head < alist.last; alist.head++)
59 			freename(alist.head->fname);
60 		nextarg = NULL;
61 		volno = 0;
62 	}
63 	getcmd(curdir, cmd, name, &alist);
64 	switch (cmd[0]) {
65 	/*
66 	 * Add elements to the extraction list.
67 	 */
68 	case 'a':
69 		if (strncmp(cmd, "add", strlen(cmd)) != 0)
70 			goto bad;
71 		ino = dirlookup(name);
72 		if (ino == 0)
73 			break;
74 		if (mflag)
75 			pathcheck(name);
76 		treescan(name, ino, addfile);
77 		break;
78 	/*
79 	 * Change working directory.
80 	 */
81 	case 'c':
82 		if (strncmp(cmd, "cd", strlen(cmd)) != 0)
83 			goto bad;
84 		ino = dirlookup(name);
85 		if (ino == 0)
86 			break;
87 		if (inodetype(ino) == LEAF) {
88 			fprintf(stderr, "%s: not a directory\n", name);
89 			break;
90 		}
91 		(void) strcpy(curdir, name);
92 		break;
93 	/*
94 	 * Delete elements from the extraction list.
95 	 */
96 	case 'd':
97 		if (strncmp(cmd, "delete", strlen(cmd)) != 0)
98 			goto bad;
99 		np = lookupname(name);
100 		if (np == NIL || (np->e_flags & NEW) == 0) {
101 			fprintf(stderr, "%s: not on extraction list\n", name);
102 			break;
103 		}
104 		treescan(name, np->e_ino, deletefile);
105 		break;
106 	/*
107 	 * Extract the requested list.
108 	 */
109 	case 'e':
110 		if (strncmp(cmd, "extract", strlen(cmd)) != 0)
111 			goto bad;
112 		createfiles();
113 		createlinks();
114 		setdirmodes();
115 		if (dflag)
116 			checkrestore();
117 		volno = 0;
118 		break;
119 	/*
120 	 * List available commands.
121 	 */
122 	case 'h':
123 		if (strncmp(cmd, "help", strlen(cmd)) != 0)
124 			goto bad;
125 	case '?':
126 		fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
127 			"Available commands are:\n",
128 			"\tls [arg] - list directory\n",
129 			"\tcd arg - change directory\n",
130 			"\tpwd - print current directory\n",
131 			"\tadd [arg] - add `arg' to list of",
132 			" files to be extracted\n",
133 			"\tdelete [arg] - delete `arg' from",
134 			" list of files to be extracted\n",
135 			"\textract - extract requested files\n",
136 			"\tsetmodes - set modes of requested directories\n",
137 			"\tquit - immediately exit program\n",
138 			"\twhat - list dump header information\n",
139 			"\tverbose - toggle verbose flag",
140 			" (useful with ``ls'')\n",
141 			"\thelp or `?' - print this list\n",
142 			"If no `arg' is supplied, the current",
143 			" directory is used\n");
144 		break;
145 	/*
146 	 * List a directory.
147 	 */
148 	case 'l':
149 		if (strncmp(cmd, "ls", strlen(cmd)) != 0)
150 			goto bad;
151 		ino = dirlookup(name);
152 		if (ino == 0)
153 			break;
154 		printlist(name, ino, curdir);
155 		break;
156 	/*
157 	 * Print current directory.
158 	 */
159 	case 'p':
160 		if (strncmp(cmd, "pwd", strlen(cmd)) != 0)
161 			goto bad;
162 		if (curdir[1] == '\0')
163 			fprintf(stderr, "/\n");
164 		else
165 			fprintf(stderr, "%s\n", &curdir[1]);
166 		break;
167 	/*
168 	 * Quit.
169 	 */
170 	case 'q':
171 		if (strncmp(cmd, "quit", strlen(cmd)) != 0)
172 			goto bad;
173 		return;
174 	case 'x':
175 		if (strncmp(cmd, "xit", strlen(cmd)) != 0)
176 			goto bad;
177 		return;
178 	/*
179 	 * Toggle verbose mode.
180 	 */
181 	case 'v':
182 		if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
183 			goto bad;
184 		if (vflag) {
185 			fprintf(stderr, "verbose mode off\n");
186 			vflag = 0;
187 			break;
188 		}
189 		fprintf(stderr, "verbose mode on\n");
190 		vflag++;
191 		break;
192 	/*
193 	 * Just restore requested directory modes.
194 	 */
195 	case 's':
196 		if (strncmp(cmd, "setmodes", strlen(cmd)) != 0)
197 			goto bad;
198 		setdirmodes();
199 		break;
200 	/*
201 	 * Print out dump header information.
202 	 */
203 	case 'w':
204 		if (strncmp(cmd, "what", strlen(cmd)) != 0)
205 			goto bad;
206 		printdumpinfo();
207 		break;
208 	/*
209 	 * Turn on debugging.
210 	 */
211 	case 'D':
212 		if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
213 			goto bad;
214 		if (dflag) {
215 			fprintf(stderr, "debugging mode off\n");
216 			dflag = 0;
217 			break;
218 		}
219 		fprintf(stderr, "debugging mode on\n");
220 		dflag++;
221 		break;
222 	/*
223 	 * Unknown command.
224 	 */
225 	default:
226 	bad:
227 		fprintf(stderr, "%s: unknown command; type ? for help\n", cmd);
228 		break;
229 	}
230 	goto loop;
231 }
232 
233 /*
234  * Read and parse an interactive command.
235  * The first word on the line is assigned to "cmd". If
236  * there are no arguments on the command line, then "curdir"
237  * is returned as the argument. If there are arguments
238  * on the line they are returned one at a time on each
239  * successive call to getcmd. Each argument is first assigned
240  * to "name". If it does not start with "/" the pathname in
241  * "curdir" is prepended to it. Finally "canon" is called to
242  * eliminate any embedded ".." components.
243  */
244 getcmd(curdir, cmd, name, ap)
245 	char *curdir, *cmd, *name;
246 	struct arglist *ap;
247 {
248 	register char *cp;
249 	static char input[BUFSIZ];
250 	char output[BUFSIZ];
251 #	define rawname input	/* save space by reusing input buffer */
252 
253 	/*
254 	 * Check to see if still processing arguments.
255 	 */
256 	if (ap->head != ap->last) {
257 		strcpy(name, ap->head->fname);
258 		freename(ap->head->fname);
259 		ap->head++;
260 		return;
261 	}
262 	if (nextarg != NULL)
263 		goto getnext;
264 	/*
265 	 * Read a command line and trim off trailing white space.
266 	 */
267 	do	{
268 		fprintf(stderr, "restore > ");
269 		(void) fflush(stderr);
270 		(void) fgets(input, BUFSIZ, terminal);
271 	} while (!feof(terminal) && input[0] == '\n');
272 	if (feof(terminal)) {
273 		(void) strcpy(cmd, "quit");
274 		return;
275 	}
276 	for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
277 		/* trim off trailing white space and newline */;
278 	*++cp = '\0';
279 	/*
280 	 * Copy the command into "cmd".
281 	 */
282 	cp = copynext(input, cmd);
283 	ap->cmd = cmd;
284 	/*
285 	 * If no argument, use curdir as the default.
286 	 */
287 	if (*cp == '\0') {
288 		(void) strcpy(name, curdir);
289 		return;
290 	}
291 	nextarg = cp;
292 	/*
293 	 * Find the next argument.
294 	 */
295 getnext:
296 	cp = copynext(nextarg, rawname);
297 	if (*cp == '\0')
298 		nextarg = NULL;
299 	else
300 		nextarg = cp;
301 	/*
302 	 * If it an absolute pathname, canonicalize it and return it.
303 	 */
304 	if (rawname[0] == '/') {
305 		canon(rawname, name);
306 	} else {
307 		/*
308 		 * For relative pathnames, prepend the current directory to
309 		 * it then canonicalize and return it.
310 		 */
311 		(void) strcpy(output, curdir);
312 		(void) strcat(output, "/");
313 		(void) strcat(output, rawname);
314 		canon(output, name);
315 	}
316 	expandarg(name, ap);
317 	strcpy(name, ap->head->fname);
318 	freename(ap->head->fname);
319 	ap->head++;
320 #	undef rawname
321 }
322 
323 /*
324  * Strip off the next token of the input.
325  */
326 char *
327 copynext(input, output)
328 	char *input, *output;
329 {
330 	register char *cp, *bp;
331 	char quote;
332 
333 	for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
334 		/* skip to argument */;
335 	bp = output;
336 	while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
337 		/*
338 		 * Handle back slashes.
339 		 */
340 		if (*cp == '\\') {
341 			if (*++cp == '\0') {
342 				fprintf(stderr,
343 					"command lines cannot be continued\n");
344 				continue;
345 			}
346 			*bp++ = *cp++;
347 			continue;
348 		}
349 		/*
350 		 * The usual unquoted case.
351 		 */
352 		if (*cp != '\'' && *cp != '"') {
353 			*bp++ = *cp++;
354 			continue;
355 		}
356 		/*
357 		 * Handle single and double quotes.
358 		 */
359 		quote = *cp++;
360 		while (*cp != quote && *cp != '\0')
361 			*bp++ = *cp++ | 0200;
362 		if (*cp++ == '\0') {
363 			fprintf(stderr, "missing %c\n", quote);
364 			cp--;
365 			continue;
366 		}
367 	}
368 	*bp = '\0';
369 	return (cp);
370 }
371 
372 /*
373  * Canonicalize file names to always start with ``./'' and
374  * remove any imbedded "." and ".." components.
375  */
376 canon(rawname, canonname)
377 	char *rawname, *canonname;
378 {
379 	register char *cp, *np;
380 	int len;
381 
382 	if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
383 		(void) strcpy(canonname, "");
384 	else if (rawname[0] == '/')
385 		(void) strcpy(canonname, ".");
386 	else
387 		(void) strcpy(canonname, "./");
388 	(void) strcat(canonname, rawname);
389 	/*
390 	 * Eliminate multiple and trailing '/'s
391 	 */
392 	for (cp = np = canonname; *np != '\0'; cp++) {
393 		*cp = *np++;
394 		while (*cp == '/' && *np == '/')
395 			np++;
396 	}
397 	*cp = '\0';
398 	if (*--cp == '/')
399 		*cp = '\0';
400 	/*
401 	 * Eliminate extraneous "." and ".." from pathnames.
402 	 */
403 	for (np = canonname; *np != '\0'; ) {
404 		np++;
405 		cp = np;
406 		while (*np != '/' && *np != '\0')
407 			np++;
408 		if (np - cp == 1 && *cp == '.') {
409 			cp--;
410 			(void) strcpy(cp, np);
411 			np = cp;
412 		}
413 		if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
414 			cp--;
415 			while (cp > &canonname[1] && *--cp != '/')
416 				/* find beginning of name */;
417 			(void) strcpy(cp, np);
418 			np = cp;
419 		}
420 	}
421 }
422 
423 /*
424  * globals (file name generation)
425  *
426  * "*" in params matches r.e ".*"
427  * "?" in params matches r.e. "."
428  * "[...]" in params matches character class
429  * "[...a-z...]" in params matches a through z.
430  */
431 expandarg(arg, ap)
432 	char *arg;
433 	register struct arglist *ap;
434 {
435 	static struct afile single;
436 	struct entry *ep;
437 	int size;
438 
439 	ap->head = ap->last = (struct afile *)0;
440 	size = expand(arg, 0, ap);
441 	if (size == 0) {
442 		ep = lookupname(arg);
443 		single.fnum = ep ? ep->e_ino : 0;
444 		single.fname = savename(arg);
445 		ap->head = &single;
446 		ap->last = ap->head + 1;
447 		return;
448 	}
449 	qsort((char *)ap->head, ap->last - ap->head, sizeof *ap->head, fcmp);
450 }
451 
452 /*
453  * Expand a file name
454  */
455 expand(as, rflg, ap)
456 	char *as;
457 	int rflg;
458 	register struct arglist *ap;
459 {
460 	int		count, size;
461 	char		dir = 0;
462 	char		*rescan = 0;
463 	DIR		*dirp;
464 	register char	*s, *cs;
465 	int		sindex, rindex, lindex;
466 	struct direct	*dp;
467 	register char	slash;
468 	register char	*rs;
469 	register char	c;
470 
471 	/*
472 	 * check for meta chars
473 	 */
474 	s = cs = as;
475 	slash = 0;
476 	while (*cs != '*' && *cs != '?' && *cs != '[') {
477 		if (*cs++ == 0) {
478 			if (rflg && slash)
479 				break;
480 			else
481 				return (0) ;
482 		} else if (*cs == '/') {
483 			slash++;
484 		}
485 	}
486 	for (;;) {
487 		if (cs == s) {
488 			s = "";
489 			break;
490 		} else if (*--cs == '/') {
491 			*cs = 0;
492 			if (s == cs)
493 				s = "/";
494 			break;
495 		}
496 	}
497 	if ((dirp = rst_opendir(s)) != NULL)
498 		dir++;
499 	count = 0;
500 	if (*cs == 0)
501 		*cs++ = 0200;
502 	if (dir) {
503 		/*
504 		 * check for rescan
505 		 */
506 		rs = cs;
507 		do {
508 			if (*rs == '/') {
509 				rescan = rs;
510 				*rs = 0;
511 			}
512 		} while (*rs++);
513 		sindex = ap->last - ap->head;
514 		while ((dp = rst_readdir(dirp)) != NULL && dp->d_ino != 0) {
515 			if (!dflag && BIT(dp->d_ino, dumpmap) == 0)
516 				continue;
517 			if ((*dp->d_name == '.' && *cs != '.'))
518 				continue;
519 			if (gmatch(dp->d_name, cs)) {
520 				if (addg(dp, s, rescan, ap) < 0)
521 					return (-1);
522 				count++;
523 			}
524 		}
525 		if (rescan) {
526 			rindex = sindex;
527 			lindex = ap->last - ap->head;
528 			if (count) {
529 				count = 0;
530 				while (rindex < lindex) {
531 					size = expand(ap->head[rindex].fname,
532 					    1, ap);
533 					if (size < 0)
534 						return (size);
535 					count += size;
536 					rindex++;
537 				}
538 			}
539 			bcopy((char *)&ap->head[lindex],
540 			     (char *)&ap->head[sindex],
541 			     (ap->last - &ap->head[rindex]) * sizeof *ap->head);
542 			ap->last -= lindex - sindex;
543 			*rescan = '/';
544 		}
545 	}
546 	s = as;
547 	while (c = *s)
548 		*s++ = (c&0177 ? c : '/');
549 	return (count);
550 }
551 
552 /*
553  * Check for a name match
554  */
555 gmatch(s, p)
556 	register char	*s, *p;
557 {
558 	register int	scc;
559 	char		c;
560 	char		ok;
561 	int		lc;
562 
563 	if (scc = *s++)
564 		if ((scc &= 0177) == 0)
565 			scc = 0200;
566 	switch (c = *p++) {
567 
568 	case '[':
569 		ok = 0;
570 		lc = 077777;
571 		while (c = *p++) {
572 			if (c == ']') {
573 				return (ok ? gmatch(s, p) : 0);
574 			} else if (c == '-') {
575 				if (lc <= scc && scc <= (*p++))
576 					ok++ ;
577 			} else {
578 				if (scc == (lc = (c&0177)))
579 					ok++ ;
580 			}
581 		}
582 		return (0);
583 
584 	default:
585 		if ((c&0177) != scc)
586 			return (0) ;
587 		/* falls through */
588 
589 	case '?':
590 		return (scc ? gmatch(s, p) : 0);
591 
592 	case '*':
593 		if (*p == 0)
594 			return (1) ;
595 		s--;
596 		while (*s) {
597 			if (gmatch(s++, p))
598 				return (1);
599 		}
600 		return (0);
601 
602 	case 0:
603 		return (scc == 0);
604 	}
605 }
606 
607 /*
608  * Construct a matched name.
609  */
610 addg(dp, as1, as3, ap)
611 	struct direct	*dp;
612 	char		*as1, *as3;
613 	struct arglist	*ap;
614 {
615 	register char	*s1, *s2;
616 	register int	c;
617 	char		buf[BUFSIZ];
618 
619 	s2 = buf;
620 	s1 = as1;
621 	while (c = *s1++) {
622 		if ((c &= 0177) == 0) {
623 			*s2++ = '/';
624 			break;
625 		}
626 		*s2++ = c;
627 	}
628 	s1 = dp->d_name;
629 	while (*s2 = *s1++)
630 		s2++;
631 	if (s1 = as3) {
632 		*s2++ = '/';
633 		while (*s2++ = *++s1)
634 			/* void */;
635 	}
636 	if (mkentry(buf, dp->d_ino, ap) == FAIL)
637 		return (-1);
638 }
639 
640 /*
641  * Do an "ls" style listing of a directory
642  */
643 printlist(name, ino, basename)
644 	char *name;
645 	ino_t ino;
646 	char *basename;
647 {
648 	register struct afile *fp;
649 	register struct direct *dp;
650 	static struct arglist alist = { 0, 0, 0, 0, "ls" };
651 	struct afile single;
652 	DIR *dirp;
653 
654 	if ((dirp = rst_opendir(name)) == NULL) {
655 		single.fnum = ino;
656 		single.fname = savename(name + strlen(basename) + 1);
657 		alist.head = &single;
658 		alist.last = alist.head + 1;
659 	} else {
660 		alist.head = (struct afile *)0;
661 		fprintf(stderr, "%s:\n", name);
662 		while (dp = rst_readdir(dirp)) {
663 			if (dp == NULL || dp->d_ino == 0)
664 				break;
665 			if (!dflag && BIT(dp->d_ino, dumpmap) == 0)
666 				continue;
667 			if (vflag == 0 &&
668 			    (strcmp(dp->d_name, ".") == 0 ||
669 			     strcmp(dp->d_name, "..") == 0))
670 				continue;
671 			if (!mkentry(dp->d_name, dp->d_ino, &alist))
672 				return;
673 		}
674 	}
675 	if (alist.head != 0) {
676 		qsort((char *)alist.head, alist.last - alist.head,
677 			sizeof *alist.head, fcmp);
678 		formatf(&alist);
679 		for (fp = alist.head; fp < alist.last; fp++)
680 			freename(fp->fname);
681 	}
682 	if (dirp != NULL)
683 		fprintf(stderr, "\n");
684 }
685 
686 /*
687  * Read the contents of a directory.
688  */
689 mkentry(name, ino, ap)
690 	char *name;
691 	ino_t ino;
692 	register struct arglist *ap;
693 {
694 	register struct afile *fp;
695 
696 	if (ap->base == NULL) {
697 		ap->nent = 20;
698 		ap->base = (struct afile *)calloc((unsigned)ap->nent,
699 			sizeof (struct afile));
700 		if (ap->base == NULL) {
701 			fprintf(stderr, "%s: out of memory\n", ap->cmd);
702 			return (FAIL);
703 		}
704 	}
705 	if (ap->head == 0)
706 		ap->head = ap->last = ap->base;
707 	fp = ap->last;
708 	fp->fnum = ino;
709 	fp->fname = savename(name);
710 	fp++;
711 	if (fp == ap->head + ap->nent) {
712 		ap->base = (struct afile *)realloc((char *)ap->base,
713 		    (unsigned)(2 * ap->nent * sizeof (struct afile)));
714 		if (ap->base == 0) {
715 			fprintf(stderr, "%s: out of memory\n", ap->cmd);
716 			return (FAIL);
717 		}
718 		ap->head = ap->base;
719 		fp = ap->head + ap->nent;
720 		ap->nent *= 2;
721 	}
722 	ap->last = fp;
723 	return (GOOD);
724 }
725 
726 /*
727  * Print out a pretty listing of a directory
728  */
729 formatf(ap)
730 	register struct arglist *ap;
731 {
732 	register struct afile *fp;
733 	struct entry *np;
734 	int width = 0, w, nentry = ap->last - ap->head;
735 	int i, j, len, columns, lines;
736 	char *cp;
737 
738 	if (ap->head == ap->last)
739 		return;
740 	for (fp = ap->head; fp < ap->last; fp++) {
741 		fp->ftype = inodetype(fp->fnum);
742 		np = lookupino(fp->fnum);
743 		if (np != NIL)
744 			fp->fflags = np->e_flags;
745 		else
746 			fp->fflags = 0;
747 		len = strlen(fmtentry(fp));
748 		if (len > width)
749 			width = len;
750 	}
751 	width += 2;
752 	columns = 80 / width;
753 	if (columns == 0)
754 		columns = 1;
755 	lines = (nentry + columns - 1) / columns;
756 	for (i = 0; i < lines; i++) {
757 		for (j = 0; j < columns; j++) {
758 			fp = ap->head + j * lines + i;
759 			cp = fmtentry(fp);
760 			fprintf(stderr, "%s", cp);
761 			if (fp + lines >= ap->last) {
762 				fprintf(stderr, "\n");
763 				break;
764 			}
765 			w = strlen(cp);
766 			while (w < width) {
767 				w++;
768 				fprintf(stderr, " ");
769 			}
770 		}
771 	}
772 }
773 
774 /*
775  * Comparison routine for qsort.
776  */
777 fcmp(f1, f2)
778 	register struct afile *f1, *f2;
779 {
780 
781 	return (strcmp(f1->fname, f2->fname));
782 }
783 
784 /*
785  * Format a directory entry.
786  */
787 char *
788 fmtentry(fp)
789 	register struct afile *fp;
790 {
791 	static char fmtres[BUFSIZ];
792 	static int precision = 0;
793 	int i;
794 	register char *cp, *dp;
795 
796 	if (!vflag) {
797 		fmtres[0] = '\0';
798 	} else {
799 		if (precision == 0)
800 			for (i = maxino; i > 0; i /= 10)
801 				precision++;
802 		(void) sprintf(fmtres, "%*d ", precision, fp->fnum);
803 	}
804 	dp = &fmtres[strlen(fmtres)];
805 	if (dflag && BIT(fp->fnum, dumpmap) == 0)
806 		*dp++ = '^';
807 	else if ((fp->fflags & NEW) != 0)
808 		*dp++ = '*';
809 	else
810 		*dp++ = ' ';
811 	for (cp = fp->fname; *cp; cp++)
812 		if (!vflag && (*cp < ' ' || *cp >= 0177))
813 			*dp++ = '?';
814 		else
815 			*dp++ = *cp;
816 	if (fp->ftype == NODE)
817 		*dp++ = '/';
818 	*dp++ = 0;
819 	return (fmtres);
820 }
821 
822 /*
823  * respond to interrupts
824  */
825 onintr()
826 {
827 	if (command == 'i')
828 		longjmp(reset, 1);
829 	if (reply("restore interrupted, continue") == FAIL)
830 		done(1);
831 }
832