Initial import from FreeBSD RELENG_4:
[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  * 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: src/sbin/restore/interactive.c,v 1.8.2.1 2001/01/03 14:36:08 iedowse Exp $";
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 __P((char *, char *));
85 static int       fcmp __P((const void *, const void *));
86 static void      formatf __P((struct afile *, int));
87 static void      getcmd __P((char *, char *, char *, int, struct arglist *));
88 struct dirent   *glob_readdir __P((RST_DIR *dirp));
89 static int       glob_stat __P((const char *, struct stat *));
90 static void      mkentry __P((char *, struct direct *, struct afile *));
91 static void      printlist __P((char *, char *));
92
93 /*
94  * Read and execute commands from the terminal.
95  */
96 void
97 runcmdshell()
98 {
99         register 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(curdir, cmd, name, size, ap)
306         char *curdir, *cmd, *name;
307         struct arglist *ap;
308         int size;
309 {
310         register char *cp;
311         static char input[BUFSIZ];
312         char output[BUFSIZ];
313 #       define rawname input    /* save space by reusing input buffer */
314
315         /*
316          * Check to see if still processing arguments.
317          */
318         if (ap->argcnt > 0)
319                 goto retnext;
320         if (nextarg != NULL)
321                 goto getnext;
322         /*
323          * Read a command line and trim off trailing white space.
324          */
325         do      {
326                 fprintf(stderr, "restore > ");
327                 (void) fflush(stderr);
328                 if (fgets(input, BUFSIZ, terminal) == NULL) {
329                         strcpy(cmd, "quit");
330                         return;
331                 }
332         } while (input[0] == '\n');
333         for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--)
334                 /* trim off trailing white space and newline */;
335         *++cp = '\0';
336         /*
337          * Copy the command into "cmd".
338          */
339         cp = copynext(input, cmd);
340         ap->cmd = cmd;
341         /*
342          * If no argument, use curdir as the default.
343          */
344         if (*cp == '\0') {
345                 (void) strncpy(name, curdir, size);
346                 name[size - 1] = '\0';
347                 return;
348         }
349         nextarg = cp;
350         /*
351          * Find the next argument.
352          */
353 getnext:
354         cp = copynext(nextarg, rawname);
355         if (*cp == '\0')
356                 nextarg = NULL;
357         else
358                 nextarg = cp;
359         /*
360          * If it is an absolute pathname, canonicalize it and return it.
361          */
362         if (rawname[0] == '/') {
363                 canon(rawname, name, size);
364         } else {
365                 /*
366                  * For relative pathnames, prepend the current directory to
367                  * it then canonicalize and return it.
368                  */
369                 snprintf(output, sizeof(output), "%s/%s", curdir, rawname);
370                 canon(output, name, size);
371         }
372         if (glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob) < 0)
373                 fprintf(stderr, "%s: out of memory\n", ap->cmd);
374         if (ap->glob.gl_pathc == 0)
375                 return;
376         ap->freeglob = 1;
377         ap->argcnt = ap->glob.gl_pathc;
378
379 retnext:
380         strncpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt], size);
381         name[size - 1] = '\0';
382         if (--ap->argcnt == 0) {
383                 ap->freeglob = 0;
384                 globfree(&ap->glob);
385         }
386 #       undef rawname
387 }
388
389 /*
390  * Strip off the next token of the input.
391  */
392 static char *
393 copynext(input, output)
394         char *input, *output;
395 {
396         register char *cp, *bp;
397         char quote;
398
399         for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
400                 /* skip to argument */;
401         bp = output;
402         while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
403                 /*
404                  * Handle back slashes.
405                  */
406                 if (*cp == '\\') {
407                         if (*++cp == '\0') {
408                                 fprintf(stderr,
409                                         "command lines cannot be continued\n");
410                                 continue;
411                         }
412                         *bp++ = *cp++;
413                         continue;
414                 }
415                 /*
416                  * The usual unquoted case.
417                  */
418                 if (*cp != '\'' && *cp != '"') {
419                         *bp++ = *cp++;
420                         continue;
421                 }
422                 /*
423                  * Handle single and double quotes.
424                  */
425                 quote = *cp++;
426                 while (*cp != quote && *cp != '\0')
427                         *bp++ = *cp++ | 0200;
428                 if (*cp++ == '\0') {
429                         fprintf(stderr, "missing %c\n", quote);
430                         cp--;
431                         continue;
432                 }
433         }
434         *bp = '\0';
435         return (cp);
436 }
437
438 /*
439  * Canonicalize file names to always start with ``./'' and
440  * remove any embedded "." and ".." components.
441  */
442 void
443 canon(rawname, canonname, len)
444         char *rawname, *canonname;
445         int len;
446 {
447         register char *cp, *np;
448
449         if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0)
450                 (void) strcpy(canonname, "");
451         else if (rawname[0] == '/')
452                 (void) strcpy(canonname, ".");
453         else
454                 (void) strcpy(canonname, "./");
455         if (strlen(canonname) + strlen(rawname) >= len) {
456                 fprintf(stderr, "canonname: not enough buffer space\n");
457                 done(1);
458         }
459                 
460         (void) strcat(canonname, rawname);
461         /*
462          * Eliminate multiple and trailing '/'s
463          */
464         for (cp = np = canonname; *np != '\0'; cp++) {
465                 *cp = *np++;
466                 while (*cp == '/' && *np == '/')
467                         np++;
468         }
469         *cp = '\0';
470         if (*--cp == '/')
471                 *cp = '\0';
472         /*
473          * Eliminate extraneous "." and ".." from pathnames.
474          */
475         for (np = canonname; *np != '\0'; ) {
476                 np++;
477                 cp = np;
478                 while (*np != '/' && *np != '\0')
479                         np++;
480                 if (np - cp == 1 && *cp == '.') {
481                         cp--;
482                         (void) strcpy(cp, np);
483                         np = cp;
484                 }
485                 if (np - cp == 2 && strncmp(cp, "..", 2) == 0) {
486                         cp--;
487                         while (cp > &canonname[1] && *--cp != '/')
488                                 /* find beginning of name */;
489                         (void) strcpy(cp, np);
490                         np = cp;
491                 }
492         }
493 }
494
495 /*
496  * Do an "ls" style listing of a directory
497  */
498 static void
499 printlist(name, basename)
500         char *name;
501         char *basename;
502 {
503         register struct afile *fp, *list, *listp;
504         register struct direct *dp;
505         struct afile single;
506         RST_DIR *dirp;
507         int entries, len, namelen;
508         char locname[MAXPATHLEN + 1];
509
510         dp = pathsearch(name);
511         if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
512             (!vflag && dp->d_ino == WINO))
513                 return;
514         if ((dirp = rst_opendir(name)) == NULL) {
515                 entries = 1;
516                 list = &single;
517                 mkentry(name, dp, list);
518                 len = strlen(basename) + 1;
519                 if (strlen(name) - len > single.len) {
520                         freename(single.fname);
521                         single.fname = savename(&name[len]);
522                         single.len = strlen(single.fname);
523                 }
524         } else {
525                 entries = 0;
526                 while ((dp = rst_readdir(dirp)))
527                         entries++;
528                 rst_closedir(dirp);
529                 list = (struct afile *)malloc(entries * sizeof(struct afile));
530                 if (list == NULL) {
531                         fprintf(stderr, "ls: out of memory\n");
532                         return;
533                 }
534                 if ((dirp = rst_opendir(name)) == NULL)
535                         panic("directory reopen failed\n");
536                 fprintf(stderr, "%s:\n", name);
537                 entries = 0;
538                 listp = list;
539                 (void) strncpy(locname, name, MAXPATHLEN);
540                 (void) strncat(locname, "/", MAXPATHLEN);
541                 namelen = strlen(locname);
542                 while ((dp = rst_readdir(dirp))) {
543                         if (dp == NULL)
544                                 break;
545                         if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
546                                 continue;
547                         if (!vflag && (dp->d_ino == WINO ||
548                              strcmp(dp->d_name, ".") == 0 ||
549                              strcmp(dp->d_name, "..") == 0))
550                                 continue;
551                         locname[namelen] = '\0';
552                         if (namelen + dp->d_namlen >= MAXPATHLEN) {
553                                 fprintf(stderr, "%s%s: name exceeds %d char\n",
554                                         locname, dp->d_name, MAXPATHLEN);
555                         } else {
556                                 (void) strncat(locname, dp->d_name,
557                                     (int)dp->d_namlen);
558                                 mkentry(locname, dp, listp++);
559                                 entries++;
560                         }
561                 }
562                 rst_closedir(dirp);
563                 if (entries == 0) {
564                         fprintf(stderr, "\n");
565                         free(list);
566                         return;
567                 }
568                 qsort((char *)list, entries, sizeof(struct afile), fcmp);
569         }
570         formatf(list, entries);
571         if (dirp != NULL) {
572                 for (fp = listp - 1; fp >= list; fp--)
573                         freename(fp->fname);
574                 fprintf(stderr, "\n");
575                 free(list);
576         }
577 }
578
579 /*
580  * Read the contents of a directory.
581  */
582 static void
583 mkentry(name, dp, fp)
584         char *name;
585         struct direct *dp;
586         register struct afile *fp;
587 {
588         char *cp;
589         struct entry *np;
590
591         fp->fnum = dp->d_ino;
592         fp->fname = savename(dp->d_name);
593         for (cp = fp->fname; *cp; cp++)
594                 if (!vflag && (*cp < ' ' || *cp >= 0177))
595                         *cp = '?';
596         fp->len = cp - fp->fname;
597         if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
598                 fp->prefix = '^';
599         else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
600                 fp->prefix = '*';
601         else
602                 fp->prefix = ' ';
603         switch(dp->d_type) {
604
605         default:
606                 fprintf(stderr, "Warning: undefined file type %d\n",
607                     dp->d_type);
608                 /* fall through */
609         case DT_REG:
610                 fp->postfix = ' ';
611                 break;
612
613         case DT_LNK:
614                 fp->postfix = '@';
615                 break;
616
617         case DT_FIFO:
618         case DT_SOCK:
619                 fp->postfix = '=';
620                 break;
621
622         case DT_CHR:
623         case DT_BLK:
624                 fp->postfix = '#';
625                 break;
626
627         case DT_WHT:
628                 fp->postfix = '%';
629                 break;
630
631         case DT_UNKNOWN:
632         case DT_DIR:
633                 if (inodetype(dp->d_ino) == NODE)
634                         fp->postfix = '/';
635                 else
636                         fp->postfix = ' ';
637                 break;
638         }
639         return;
640 }
641
642 /*
643  * Print out a pretty listing of a directory
644  */
645 static void
646 formatf(list, nentry)
647         register struct afile *list;
648         int nentry;
649 {
650         register struct afile *fp, *endlist;
651         int width, bigino, haveprefix, havepostfix;
652         int i, j, w, precision, columns, lines;
653
654         width = 0;
655         haveprefix = 0;
656         havepostfix = 0;
657         bigino = ROOTINO;
658         endlist = &list[nentry];
659         for (fp = &list[0]; fp < endlist; fp++) {
660                 if (bigino < fp->fnum)
661                         bigino = fp->fnum;
662                 if (width < fp->len)
663                         width = fp->len;
664                 if (fp->prefix != ' ')
665                         haveprefix = 1;
666                 if (fp->postfix != ' ')
667                         havepostfix = 1;
668         }
669         if (haveprefix)
670                 width++;
671         if (havepostfix)
672                 width++;
673         if (vflag) {
674                 for (precision = 0, i = bigino; i > 0; i /= 10)
675                         precision++;
676                 width += precision + 1;
677         }
678         width++;
679         columns = 81 / width;
680         if (columns == 0)
681                 columns = 1;
682         lines = (nentry + columns - 1) / columns;
683         for (i = 0; i < lines; i++) {
684                 for (j = 0; j < columns; j++) {
685                         fp = &list[j * lines + i];
686                         if (vflag) {
687                                 fprintf(stderr, "%*d ", precision, fp->fnum);
688                                 fp->len += precision + 1;
689                         }
690                         if (haveprefix) {
691                                 putc(fp->prefix, stderr);
692                                 fp->len++;
693                         }
694                         fprintf(stderr, "%s", fp->fname);
695                         if (havepostfix) {
696                                 putc(fp->postfix, stderr);
697                                 fp->len++;
698                         }
699                         if (fp + lines >= endlist) {
700                                 fprintf(stderr, "\n");
701                                 break;
702                         }
703                         for (w = fp->len; w < width; w++)
704                                 putc(' ', stderr);
705                 }
706         }
707 }
708
709 /*
710  * Skip over directory entries that are not on the tape
711  *
712  * First have to get definition of a dirent.
713  */
714 #undef DIRBLKSIZ
715 #include <dirent.h>
716 #undef d_ino
717
718 struct dirent *
719 glob_readdir(dirp)
720         RST_DIR *dirp;
721 {
722         struct direct *dp;
723         static struct dirent adirent;
724
725         while ((dp = rst_readdir(dirp)) != NULL) {
726                 if (!vflag && dp->d_ino == WINO)
727                         continue;
728                 if (dflag || TSTINO(dp->d_ino, dumpmap))
729                         break;
730         }
731         if (dp == NULL)
732                 return (NULL);
733         adirent.d_fileno = dp->d_ino;
734         adirent.d_namlen = dp->d_namlen;
735         memmove(adirent.d_name, dp->d_name, dp->d_namlen + 1);
736         return (&adirent);
737 }
738
739 /*
740  * Return st_mode information in response to stat or lstat calls
741  */
742 static int
743 glob_stat(name, stp)
744         const char *name;
745         struct stat *stp;
746 {
747         register struct direct *dp;
748
749         dp = pathsearch(name);
750         if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
751             (!vflag && dp->d_ino == WINO))
752                 return (-1);
753         if (inodetype(dp->d_ino) == NODE)
754                 stp->st_mode = IFDIR;
755         else
756                 stp->st_mode = IFREG;
757         return (0);
758 }
759
760 /*
761  * Comparison routine for qsort.
762  */
763 static int
764 fcmp(f1, f2)
765         register const void *f1, *f2;
766 {
767         return (strcmp(((struct afile *)f1)->fname,
768             ((struct afile *)f2)->fname));
769 }
770
771 /*
772  * respond to interrupts
773  */
774 void
775 onintr(signo)
776         int signo;
777 {
778         if (command == 'i' && runshell)
779                 longjmp(reset, 1);
780         if (reply("restore interrupted, continue") == FAIL)
781                 done(1);
782 }