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