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