Remove advertising header from all userland binaries.
[dragonfly.git] / sbin / restore / interactive.c
1 /*
2  * Copyright (c) 1985, 1993
3  *      The Regents of the University of California.  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  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * @(#)interactive.c    8.5 (Berkeley) 5/1/95
30  * $FreeBSD: src/sbin/restore/interactive.c,v 1.8.2.1 2001/01/03 14:36:08 iedowse Exp $
31  * $DragonFly: src/sbin/restore/interactive.c,v 1.10 2005/11/06 12:49:25 swildner Exp $
32  */
33
34 #include <sys/param.h>
35 #include <sys/stat.h>
36
37 #include <dirent.h>
38 #include <setjmp.h>
39 #include <glob.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44 #include <vfs/ufs/dinode.h>
45 #include <vfs/ufs/dir.h>
46 #include <protocols/dumprestore.h>
47
48 #include "restore.h"
49 #include "extern.h"
50
51 #define round(a, b) (((a) + (b) - 1) / (b) * (b))
52
53 /*
54  * Things to handle interruptions.
55  */
56 static int runshell;
57 static jmp_buf reset;
58 static char *nextarg = NULL;
59
60 /*
61  * Structure and routines associated with listing directories.
62  */
63 struct afile {
64         ufs1_ino_t fnum;        /* inode number of file */
65         char    *fname;         /* file name */
66         short   len;            /* name length */
67         char    prefix;         /* prefix character */
68         char    postfix;        /* postfix character */
69 };
70 struct arglist {
71         int     freeglob;       /* glob structure needs to be freed */
72         int     argcnt;         /* next globbed argument to return */
73         glob_t  glob;           /* globbing information */
74         char    *cmd;           /* the current command */
75 };
76
77 static char     *copynext(char *, char *);
78 static int       fcmp(const void *, const void *);
79 static void      formatf(struct afile *, int);
80 static void      getcmd(char *, char *, char *, int, struct arglist *);
81 struct dirent   *glob_readdir(RST_DIR *dirp);
82 static int       glob_stat(const char *, struct stat *);
83 static void      mkentry(char *, struct direct *, struct afile *);
84 static void      printlist(char *, char *);
85
86 /*
87  * Read and execute commands from the terminal.
88  */
89 void
90 runcmdshell(void)
91 {
92         struct entry *np;
93         ufs1_ino_t ino;
94         struct arglist arglist;
95         char curdir[MAXPATHLEN];
96         char name[MAXPATHLEN];
97         char cmd[BUFSIZ];
98
99         arglist.freeglob = 0;
100         arglist.argcnt = 0;
101         arglist.glob.gl_flags = GLOB_ALTDIRFUNC;
102         arglist.glob.gl_opendir = (void *)rst_opendir;
103         arglist.glob.gl_readdir = (void *)glob_readdir;
104         arglist.glob.gl_closedir = (void *)rst_closedir;
105         arglist.glob.gl_lstat = glob_stat;
106         arglist.glob.gl_stat = glob_stat;
107         canon("/", curdir, sizeof(curdir));
108 loop:
109         if (setjmp(reset) != 0) {
110                 if (arglist.freeglob != 0) {
111                         arglist.freeglob = 0;
112                         arglist.argcnt = 0;
113                         globfree(&arglist.glob);
114                 }
115                 nextarg = NULL;
116                 volno = 0;
117         }
118         runshell = 1;
119         getcmd(curdir, cmd, name, sizeof(name), &arglist);
120         switch (cmd[0]) {
121         /*
122          * Add elements to the extraction list.
123          */
124         case 'a':
125                 if (strncmp(cmd, "add", strlen(cmd)) != 0)
126                         goto bad;
127                 ino = dirlookup(name);
128                 if (ino == 0)
129                         break;
130                 if (mflag)
131                         pathcheck(name);
132                 treescan(name, ino, addfile);
133                 break;
134         /*
135          * Change working directory.
136          */
137         case 'c':
138                 if (strncmp(cmd, "cd", strlen(cmd)) != 0)
139                         goto bad;
140                 ino = dirlookup(name);
141                 if (ino == 0)
142                         break;
143                 if (inodetype(ino) == LEAF) {
144                         fprintf(stderr, "%s: not a directory\n", name);
145                         break;
146                 }
147                 strcpy(curdir, name);
148                 break;
149         /*
150          * Delete elements from the extraction list.
151          */
152         case 'd':
153                 if (strncmp(cmd, "delete", strlen(cmd)) != 0)
154                         goto bad;
155                 np = lookupname(name);
156                 if (np == NULL || (np->e_flags & NEW) == 0) {
157                         fprintf(stderr, "%s: not on extraction list\n", name);
158                         break;
159                 }
160                 treescan(name, np->e_ino, deletefile);
161                 break;
162         /*
163          * Extract the requested list.
164          */
165         case 'e':
166                 if (strncmp(cmd, "extract", strlen(cmd)) != 0)
167                         goto bad;
168                 createfiles();
169                 createlinks();
170                 setdirmodes(0);
171                 if (dflag)
172                         checkrestore();
173                 volno = 0;
174                 break;
175         /*
176          * List available commands.
177          */
178         case 'h':
179                 if (strncmp(cmd, "help", strlen(cmd)) != 0)
180                         goto bad;
181         case '?':
182                 fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
183                         "Available commands are:\n",
184                         "\tls [arg] - list directory\n",
185                         "\tcd arg - change directory\n",
186                         "\tpwd - print current directory\n",
187                         "\tadd [arg] - add `arg' to list of",
188                         " files to be extracted\n",
189                         "\tdelete [arg] - delete `arg' from",
190                         " list of files to be extracted\n",
191                         "\textract - extract requested files\n",
192                         "\tsetmodes - set modes of requested directories\n",
193                         "\tquit - immediately exit program\n",
194                         "\twhat - list dump header information\n",
195                         "\tverbose - toggle verbose flag",
196                         " (useful with ``ls'')\n",
197                         "\thelp or `?' - print this list\n",
198                         "If no `arg' is supplied, the current",
199                         " directory is used\n");
200                 break;
201         /*
202          * List a directory.
203          */
204         case 'l':
205                 if (strncmp(cmd, "ls", strlen(cmd)) != 0)
206                         goto bad;
207                 printlist(name, curdir);
208                 break;
209         /*
210          * Print current directory.
211          */
212         case 'p':
213                 if (strncmp(cmd, "pwd", strlen(cmd)) != 0)
214                         goto bad;
215                 if (curdir[1] == '\0')
216                         fprintf(stderr, "/\n");
217                 else
218                         fprintf(stderr, "%s\n", &curdir[1]);
219                 break;
220         /*
221          * Quit.
222          */
223         case 'q':
224                 if (strncmp(cmd, "quit", strlen(cmd)) != 0)
225                         goto bad;
226                 return;
227         case 'x':
228                 if (strncmp(cmd, "xit", strlen(cmd)) != 0)
229                         goto bad;
230                 return;
231         /*
232          * Toggle verbose mode.
233          */
234         case 'v':
235                 if (strncmp(cmd, "verbose", strlen(cmd)) != 0)
236                         goto bad;
237                 if (vflag) {
238                         fprintf(stderr, "verbose mode off\n");
239                         vflag = 0;
240                         break;
241                 }
242                 fprintf(stderr, "verbose mode on\n");
243                 vflag++;
244                 break;
245         /*
246          * Just restore requested directory modes.
247          */
248         case 's':
249                 if (strncmp(cmd, "setmodes", strlen(cmd)) != 0)
250                         goto bad;
251                 setdirmodes(FORCE);
252                 break;
253         /*
254          * Print out dump header information.
255          */
256         case 'w':
257                 if (strncmp(cmd, "what", strlen(cmd)) != 0)
258                         goto bad;
259                 printdumpinfo();
260                 break;
261         /*
262          * Turn on debugging.
263          */
264         case 'D':
265                 if (strncmp(cmd, "Debug", strlen(cmd)) != 0)
266                         goto bad;
267                 if (dflag) {
268                         fprintf(stderr, "debugging mode off\n");
269                         dflag = 0;
270                         break;
271                 }
272                 fprintf(stderr, "debugging mode on\n");
273                 dflag++;
274                 break;
275         /*
276          * Unknown command.
277          */
278         default:
279         bad:
280                 fprintf(stderr, "%s: unknown command; type ? for help\n", cmd);
281                 break;
282         }
283         goto loop;
284 }
285
286 /*
287  * Read and parse an interactive command.
288  * The first word on the line is assigned to "cmd". If
289  * there are no arguments on the command line, then "curdir"
290  * is returned as the argument. If there are arguments
291  * on the line they are returned one at a time on each
292  * successive call to getcmd. Each argument is first assigned
293  * to "name". If it does not start with "/" the pathname in
294  * "curdir" is prepended to it. Finally "canon" is called to
295  * eliminate any embedded ".." components.
296  */
297 static void
298 getcmd(char *curdir, char *cmd, char *name, int size, struct arglist *ap)
299 {
300         char *cp;
301         static char input[BUFSIZ];
302         char output[BUFSIZ];
303 #       define rawname input    /* save space by reusing input buffer */
304
305         /*
306          * Check to see if still processing arguments.
307          */
308         if (ap->argcnt > 0)
309                 goto retnext;
310         if (nextarg != NULL)
311                 goto getnext;
312         /*
313          * Read a command line and trim off trailing white space.
314          */
315         do      {
316                 fprintf(stderr, "restore > ");
317                 fflush(stderr);
318                 if (fgets(input, BUFSIZ, terminal) == NULL) {
319                         strcpy(cmd, "quit");
320                         return;
321                 }
322         } while (input[0] == '\n');
323         for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
324                 /* trim off trailing white space and newline */;
325         *++cp = '\0';
326         /*
327          * Copy the command into "cmd".
328          */
329         cp = copynext(input, cmd);
330         ap->cmd = cmd;
331         /*
332          * If no argument, use curdir as the default.
333          */
334         if (*cp == '\0') {
335                 strncpy(name, curdir, size);
336                 name[size - 1] = '\0';
337                 return;
338         }
339         nextarg = cp;
340         /*
341          * Find the next argument.
342          */
343 getnext:
344         cp = copynext(nextarg, rawname);
345         if (*cp == '\0')
346                 nextarg = NULL;
347         else
348                 nextarg = cp;
349         /*
350          * If it is an absolute pathname, canonicalize it and return it.
351          */
352         if (rawname[0] == '/') {
353                 canon(rawname, name, size);
354         } else {
355                 /*
356                  * For relative pathnames, prepend the current directory to
357                  * it then canonicalize and return it.
358                  */
359                 snprintf(output, sizeof(output), "%s/%s", curdir, rawname);
360                 canon(output, name, size);
361         }
362         if (glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob) < 0)
363                 fprintf(stderr, "%s: out of memory\n", ap->cmd);
364         if (ap->glob.gl_pathc == 0)
365                 return;
366         ap->freeglob = 1;
367         ap->argcnt = ap->glob.gl_pathc;
368
369 retnext:
370         strncpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt], size);
371         name[size - 1] = '\0';
372         if (--ap->argcnt == 0) {
373                 ap->freeglob = 0;
374                 globfree(&ap->glob);
375         }
376 #       undef rawname
377 }
378
379 /*
380  * Strip off the next token of the input.
381  */
382 static char *
383 copynext(char *input, char *output)
384 {
385         char *cp, *bp;
386         char quote;
387
388         for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
389                 /* skip to argument */;
390         bp = output;
391         while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
392                 /*
393                  * Handle back slashes.
394                  */
395                 if (*cp == '\\') {
396                         if (*++cp == '\0') {
397                                 fprintf(stderr,
398                                         "command lines cannot be continued\n");
399                                 continue;
400                         }
401                         *bp++ = *cp++;
402                         continue;
403                 }
404                 /*
405                  * The usual unquoted case.
406                  */
407                 if (*cp != '\'' && *cp != '"') {
408                         *bp++ = *cp++;
409                         continue;
410                 }
411                 /*
412                  * Handle single and double quotes.
413                  */
414                 quote = *cp++;
415                 while (*cp != quote && *cp != '\0')
416                         *bp++ = *cp++ | 0200;
417                 if (*cp++ == '\0') {
418                         fprintf(stderr, "missing %c\n", quote);
419                         cp--;
420                         continue;
421                 }
422         }
423         *bp = '\0';
424         return (cp);
425 }
426
427 /*
428  * Canonicalize file names to always start with ``./'' and
429  * remove any embedded "." and ".." components.
430  */
431 void
432 canon(char *rawname, char *canonname, int len)
433 {
434         char *cp, *np;
435
436         if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
437                 strcpy(canonname, "");
438         else if (rawname[0] == '/')
439                 strcpy(canonname, ".");
440         else
441                 strcpy(canonname, "./");
442         if (strlen(canonname) + strlen(rawname) >= len) {
443                 fprintf(stderr, "canonname: not enough buffer space\n");
444                 done(1);
445         }
446                 
447         strcat(canonname, rawname);
448         /*
449          * Eliminate multiple and trailing '/'s
450          */
451         for (cp = np = canonname; *np != '\0'; cp++) {
452                 *cp = *np++;
453                 while (*cp == '/' && *np == '/')
454                         np++;
455         }
456         *cp = '\0';
457         if (*--cp == '/')
458                 *cp = '\0';
459         /*
460          * Eliminate extraneous "." and ".." from pathnames.
461          */
462         for (np = canonname; *np != '\0'; ) {
463                 np++;
464                 cp = np;
465                 while (*np != '/' && *np != '\0')
466                         np++;
467                 if (np - cp == 1 && *cp == '.') {
468                         cp--;
469                         strcpy(cp, np);
470                         np = cp;
471                 }
472                 if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
473                         cp--;
474                         while (cp > &canonname[1] && *--cp != '/')
475                                 /* find beginning of name */;
476                         strcpy(cp, np);
477                         np = cp;
478                 }
479         }
480 }
481
482 /*
483  * Do an "ls" style listing of a directory
484  */
485 static void
486 printlist(char *name, char *basename)
487 {
488         struct afile *fp, *list, *listp = NULL;
489         struct direct *dp;
490         struct afile single;
491         RST_DIR *dirp;
492         int entries, len, namelen;
493         char locname[MAXPATHLEN + 1];
494
495         dp = pathsearch(name);
496         if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
497             (!vflag && dp->d_ino == WINO))
498                 return;
499         if ((dirp = rst_opendir(name)) == NULL) {
500                 entries = 1;
501                 list = &single;
502                 mkentry(name, dp, list);
503                 len = strlen(basename) + 1;
504                 if (strlen(name) - len > single.len) {
505                         freename(single.fname);
506                         single.fname = savename(&name[len]);
507                         single.len = strlen(single.fname);
508                 }
509         } else {
510                 entries = 0;
511                 while ((dp = rst_readdir(dirp)))
512                         entries++;
513                 rst_closedir(dirp);
514                 list = (struct afile *)malloc(entries * sizeof(struct afile));
515                 if (list == NULL) {
516                         fprintf(stderr, "ls: out of memory\n");
517                         return;
518                 }
519                 if ((dirp = rst_opendir(name)) == NULL)
520                         panic("directory reopen failed\n");
521                 fprintf(stderr, "%s:\n", name);
522                 entries = 0;
523                 listp = list;
524                 strncpy(locname, name, MAXPATHLEN);
525                 strncat(locname, "/", MAXPATHLEN);
526                 namelen = strlen(locname);
527                 while ((dp = rst_readdir(dirp))) {
528                         if (dp == NULL)
529                                 break;
530                         if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
531                                 continue;
532                         if (!vflag && (dp->d_ino == WINO ||
533                              strcmp(dp->d_name, ".") == 0 ||
534                              strcmp(dp->d_name, "..") == 0))
535                                 continue;
536                         locname[namelen] = '\0';
537                         if (namelen + dp->d_namlen >= MAXPATHLEN) {
538                                 fprintf(stderr, "%s%s: name exceeds %d char\n",
539                                         locname, dp->d_name, MAXPATHLEN);
540                         } else {
541                                 strncat(locname, dp->d_name, (int)dp->d_namlen);
542                                 mkentry(locname, dp, listp++);
543                                 entries++;
544                         }
545                 }
546                 rst_closedir(dirp);
547                 if (entries == 0) {
548                         fprintf(stderr, "\n");
549                         free(list);
550                         return;
551                 }
552                 qsort((char *)list, entries, sizeof(struct afile), fcmp);
553         }
554         formatf(list, entries);
555         if (dirp != NULL) {
556                 for (fp = listp - 1; fp >= list; fp--)
557                         freename(fp->fname);
558                 fprintf(stderr, "\n");
559                 free(list);
560         }
561 }
562
563 /*
564  * Read the contents of a directory.
565  */
566 static void
567 mkentry(char *name, struct direct *dp, struct afile *fp)
568 {
569         char *cp;
570         struct entry *np;
571
572         fp->fnum = dp->d_ino;
573         fp->fname = savename(dp->d_name);
574         for (cp = fp->fname; *cp; cp++)
575                 if (!vflag && (*cp < ' ' || *cp >= 0177))
576                         *cp = '?';
577         fp->len = cp - fp->fname;
578         if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
579                 fp->prefix = '^';
580         else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
581                 fp->prefix = '*';
582         else
583                 fp->prefix = ' ';
584         switch(dp->d_type) {
585
586         default:
587                 fprintf(stderr, "Warning: undefined file type %d\n",
588                     dp->d_type);
589                 /* fall through */
590         case DT_REG:
591                 fp->postfix = ' ';
592                 break;
593
594         case DT_LNK:
595                 fp->postfix = '@';
596                 break;
597
598         case DT_FIFO:
599         case DT_SOCK:
600                 fp->postfix = '=';
601                 break;
602
603         case DT_CHR:
604         case DT_BLK:
605                 fp->postfix = '#';
606                 break;
607
608         case DT_WHT:
609                 fp->postfix = '%';
610                 break;
611
612         case DT_UNKNOWN:
613         case DT_DIR:
614                 if (inodetype(dp->d_ino) == NODE)
615                         fp->postfix = '/';
616                 else
617                         fp->postfix = ' ';
618                 break;
619         }
620         return;
621 }
622
623 /*
624  * Print out a pretty listing of a directory
625  */
626 static void
627 formatf(struct afile *list, int nentry)
628 {
629         struct afile *fp, *endlist;
630         int width, bigino, haveprefix, havepostfix;
631         int i, j, w, precision = 0, columns, lines;
632
633         width = 0;
634         haveprefix = 0;
635         havepostfix = 0;
636         bigino = ROOTINO;
637         endlist = &list[nentry];
638         for (fp = &list[0]; fp < endlist; fp++) {
639                 if (bigino < fp->fnum)
640                         bigino = fp->fnum;
641                 if (width < fp->len)
642                         width = fp->len;
643                 if (fp->prefix != ' ')
644                         haveprefix = 1;
645                 if (fp->postfix != ' ')
646                         havepostfix = 1;
647         }
648         if (haveprefix)
649                 width++;
650         if (havepostfix)
651                 width++;
652         if (vflag) {
653                 for (precision = 0, i = bigino; i > 0; i /= 10)
654                         precision++;
655                 width += precision + 1;
656         }
657         width++;
658         columns = 81 / width;
659         if (columns == 0)
660                 columns = 1;
661         lines = (nentry + columns - 1) / columns;
662         for (i = 0; i < lines; i++) {
663                 for (j = 0; j < columns; j++) {
664                         fp = &list[j * lines + i];
665                         if (vflag) {
666                                 fprintf(stderr, "%*d ", precision, fp->fnum);
667                                 fp->len += precision + 1;
668                         }
669                         if (haveprefix) {
670                                 putc(fp->prefix, stderr);
671                                 fp->len++;
672                         }
673                         fprintf(stderr, "%s", fp->fname);
674                         if (havepostfix) {
675                                 putc(fp->postfix, stderr);
676                                 fp->len++;
677                         }
678                         if (fp + lines >= endlist) {
679                                 fprintf(stderr, "\n");
680                                 break;
681                         }
682                         for (w = fp->len; w < width; w++)
683                                 putc(' ', stderr);
684                 }
685         }
686 }
687
688 /*
689  * Skip over directory entries that are not on the tape
690  *
691  * First have to get definition of a dirent.
692  */
693 #undef DIRBLKSIZ
694
695 struct dirent *
696 glob_readdir(RST_DIR *dirp)
697 {
698         enum {
699                 ADIRENT_STORAGE_LEN = _DIRENT_RECLEN(NAME_MAX),
700         };
701         static union {
702                 uint8_t storage[ADIRENT_STORAGE_LEN];
703                 struct dirent alignment;
704         } adirent;
705         struct direct *dp;
706         struct dirent *adp;
707
708         while ((dp = rst_readdir(dirp)) != NULL) {
709                 if (!vflag && dp->d_ino == WINO)
710                         continue;
711                 if (dflag || TSTINO(dp->d_ino, dumpmap))
712                         break;
713         }
714         if (dp == NULL)
715                 return (NULL);
716         adp = (struct dirent *)&adirent;
717         adp->d_ino = dp->d_ino;
718         adp->d_namlen = dp->d_namlen;
719         strcpy(adp->d_name, dp->d_name);
720         return (adp);
721 }
722
723 /*
724  * Return st_mode information in response to stat or lstat calls
725  */
726 static int
727 glob_stat(const char *name, struct stat *stp)
728 {
729         struct direct *dp;
730
731         dp = pathsearch(name);
732         if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
733             (!vflag && dp->d_ino == WINO))
734                 return (-1);
735         if (inodetype(dp->d_ino) == NODE)
736                 stp->st_mode = IFDIR;
737         else
738                 stp->st_mode = IFREG;
739         return (0);
740 }
741
742 /*
743  * Comparison routine for qsort.
744  */
745 static int
746 fcmp(const void *f1, const void *f2)
747 {
748         return (strcmp(((struct afile *)f1)->fname,
749             ((struct afile *)f2)->fname));
750 }
751
752 /*
753  * respond to interrupts
754  */
755 void
756 onintr(int signo)
757 {
758         if (command == 'i' && runshell)
759                 longjmp(reset, 1);
760         if (reply("restore interrupted, continue") == FAIL)
761                 done(1);
762 }