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