]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sbin/restore/interactive.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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
30 #ifndef lint
31 #if 0
32 static char sccsid[] = "@(#)interactive.c       8.5 (Berkeley) 5/1/95";
33 #endif
34 #endif /* not lint */
35
36 #include <sys/cdefs.h>
37 __FBSDID("$FreeBSD$");
38
39 #include <sys/param.h>
40 #include <sys/stat.h>
41
42 #include <ufs/ufs/dinode.h>
43 #include <ufs/ufs/dir.h>
44 #include <protocols/dumprestore.h>
45
46 #include <ctype.h>
47 #include <glob.h>
48 #include <limits.h>
49 #include <setjmp.h>
50 #include <stdint.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 *, size_t, struct arglist *);
88 struct dirent   *glob_readdir(void *);
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 = rst_opendir;
110         arglist.glob.gl_readdir = glob_readdir;
111         arglist.glob.gl_closedir = 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, size_t 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         switch (glob(name, GLOB_ALTDIRFUNC, NULL, &ap->glob)) {
370         case GLOB_NOSPACE:
371                 fprintf(stderr, "%s: out of memory\n", ap->cmd);
372                 break;
373         case GLOB_NOMATCH:
374                 fprintf(stderr, "%s %s: no such file or directory\n", ap->cmd, name);
375                 break;
376         }
377         if (ap->glob.gl_pathc == 0)
378                 return;
379         ap->freeglob = 1;
380         ap->argcnt = ap->glob.gl_pathc;
381
382 retnext:
383         strncpy(name, ap->glob.gl_pathv[ap->glob.gl_pathc - ap->argcnt], size);
384         name[size - 1] = '\0';
385         if (--ap->argcnt == 0) {
386                 ap->freeglob = 0;
387                 globfree(&ap->glob);
388         }
389 #       undef rawname
390 }
391
392 /*
393  * Strip off the next token of the input.
394  */
395 static char *
396 copynext(char *input, char *output)
397 {
398         char *cp, *bp;
399         char quote;
400
401         for (cp = input; *cp == ' ' || *cp == '\t'; cp++)
402                 /* skip to argument */;
403         bp = output;
404         while (*cp != ' ' && *cp != '\t' && *cp != '\0') {
405                 /*
406                  * Handle back slashes.
407                  */
408                 if (*cp == '\\') {
409                         if (*++cp == '\0') {
410                                 fprintf(stderr,
411                                         "command lines cannot be continued\n");
412                                 continue;
413                         }
414                         *bp++ = *cp++;
415                         continue;
416                 }
417                 /*
418                  * The usual unquoted case.
419                  */
420                 if (*cp != '\'' && *cp != '"') {
421                         *bp++ = *cp++;
422                         continue;
423                 }
424                 /*
425                  * Handle single and double quotes.
426                  */
427                 quote = *cp++;
428                 while (*cp != quote && *cp != '\0')
429                         *bp++ = *cp++;
430                 if (*cp++ == '\0') {
431                         fprintf(stderr, "missing %c\n", quote);
432                         cp--;
433                         continue;
434                 }
435         }
436         *bp = '\0';
437         return (cp);
438 }
439
440 /*
441  * Canonicalize file names to always start with ``./'' and
442  * remove any embedded "." and ".." components.
443  */
444 void
445 canon(char *rawname, char *canonname, size_t len)
446 {
447         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(char *name, char *basename)
500 {
501         struct afile *fp, *list, *listp;
502         struct direct *dp;
503         struct afile single;
504         RST_DIR *dirp;
505         int entries, len, namelen;
506         char locname[MAXPATHLEN];
507
508         dp = pathsearch(name);
509         if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
510             (!vflag && dp->d_ino == WINO))
511                 return;
512         if ((dirp = rst_opendir(name)) == NULL) {
513                 entries = 1;
514                 list = &single;
515                 mkentry(name, dp, list);
516                 len = strlen(basename) + 1;
517                 if (strlen(name) - len > single.len) {
518                         freename(single.fname);
519                         single.fname = savename(&name[len]);
520                         single.len = strlen(single.fname);
521                 }
522         } else {
523                 entries = 0;
524                 while ((dp = rst_readdir(dirp)))
525                         entries++;
526                 rst_closedir(dirp);
527                 list = (struct afile *)malloc(entries * sizeof(struct afile));
528                 if (list == NULL) {
529                         fprintf(stderr, "ls: out of memory\n");
530                         return;
531                 }
532                 if ((dirp = rst_opendir(name)) == NULL)
533                         panic("directory reopen failed\n");
534                 fprintf(stderr, "%s:\n", name);
535                 entries = 0;
536                 listp = list;
537                 (void)strlcpy(locname, name, MAXPATHLEN);
538                 (void)strlcat(locname, "/", MAXPATHLEN);
539                 namelen = strlen(locname);
540                 while ((dp = rst_readdir(dirp))) {
541                         if (dp == NULL)
542                                 break;
543                         if (!dflag && TSTINO(dp->d_ino, dumpmap) == 0)
544                                 continue;
545                         if (!vflag && (dp->d_ino == WINO ||
546                              strcmp(dp->d_name, ".") == 0 ||
547                              strcmp(dp->d_name, "..") == 0))
548                                 continue;
549                         locname[namelen] = '\0';
550                         if (namelen + dp->d_namlen >= MAXPATHLEN) {
551                                 fprintf(stderr, "%s%s: name exceeds %d char\n",
552                                         locname, dp->d_name, MAXPATHLEN);
553                         } else {
554                                 (void)strlcat(locname, dp->d_name, MAXPATHLEN);
555                                 mkentry(locname, dp, listp++);
556                                 entries++;
557                         }
558                 }
559                 rst_closedir(dirp);
560                 if (entries == 0) {
561                         fprintf(stderr, "\n");
562                         free(list);
563                         return;
564                 }
565                 qsort((char *)list, entries, sizeof(struct afile), fcmp);
566         }
567         formatf(list, entries);
568         if (dirp != NULL) {
569                 for (fp = listp - 1; fp >= list; fp--)
570                         freename(fp->fname);
571                 fprintf(stderr, "\n");
572                 free(list);
573         }
574 }
575
576 /*
577  * Read the contents of a directory.
578  */
579 static void
580 mkentry(char *name, struct direct *dp, struct afile *fp)
581 {
582         char *cp;
583         struct entry *np;
584
585         fp->fnum = dp->d_ino;
586         fp->fname = savename(dp->d_name);
587         for (cp = fp->fname; *cp; cp++)
588                 if (!vflag && !isprint((unsigned char)*cp))
589                         *cp = '?';
590         fp->len = cp - fp->fname;
591         if (dflag && TSTINO(fp->fnum, dumpmap) == 0)
592                 fp->prefix = '^';
593         else if ((np = lookupname(name)) != NULL && (np->e_flags & NEW))
594                 fp->prefix = '*';
595         else
596                 fp->prefix = ' ';
597         switch(dp->d_type) {
598
599         default:
600                 fprintf(stderr, "Warning: undefined file type %d\n",
601                     dp->d_type);
602                 /* FALLTHROUGH */
603         case DT_REG:
604                 fp->postfix = ' ';
605                 break;
606
607         case DT_LNK:
608                 fp->postfix = '@';
609                 break;
610
611         case DT_FIFO:
612         case DT_SOCK:
613                 fp->postfix = '=';
614                 break;
615
616         case DT_CHR:
617         case DT_BLK:
618                 fp->postfix = '#';
619                 break;
620
621         case DT_WHT:
622                 fp->postfix = '%';
623                 break;
624
625         case DT_UNKNOWN:
626         case DT_DIR:
627                 if (inodetype(dp->d_ino) == NODE)
628                         fp->postfix = '/';
629                 else
630                         fp->postfix = ' ';
631                 break;
632         }
633         return;
634 }
635
636 /*
637  * Print out a pretty listing of a directory
638  */
639 static void
640 formatf(struct afile *list, int nentry)
641 {
642         struct afile *fp, *endlist;
643         int width, bigino, haveprefix, havepostfix;
644         int i, j, w, precision, columns, lines;
645
646         width = 0;
647         haveprefix = 0;
648         havepostfix = 0;
649         bigino = ROOTINO;
650         endlist = &list[nentry];
651         for (fp = &list[0]; fp < endlist; fp++) {
652                 if (bigino < fp->fnum)
653                         bigino = fp->fnum;
654                 if (width < fp->len)
655                         width = fp->len;
656                 if (fp->prefix != ' ')
657                         haveprefix = 1;
658                 if (fp->postfix != ' ')
659                         havepostfix = 1;
660         }
661         if (haveprefix)
662                 width++;
663         if (havepostfix)
664                 width++;
665         if (vflag) {
666                 for (precision = 0, i = bigino; i > 0; i /= 10)
667                         precision++;
668                 width += precision + 1;
669         }
670         width++;
671         columns = 81 / width;
672         if (columns == 0)
673                 columns = 1;
674         lines = (nentry + columns - 1) / columns;
675         for (i = 0; i < lines; i++) {
676                 for (j = 0; j < columns; j++) {
677                         fp = &list[j * lines + i];
678                         if (vflag) {
679                                 fprintf(stderr, "%*ju ",
680                                     precision, (uintmax_t)fp->fnum);
681                                 fp->len += precision + 1;
682                         }
683                         if (haveprefix) {
684                                 putc(fp->prefix, stderr);
685                                 fp->len++;
686                         }
687                         fprintf(stderr, "%s", fp->fname);
688                         if (havepostfix) {
689                                 putc(fp->postfix, stderr);
690                                 fp->len++;
691                         }
692                         if (fp + lines >= endlist) {
693                                 fprintf(stderr, "\n");
694                                 break;
695                         }
696                         for (w = fp->len; w < width; w++)
697                                 putc(' ', stderr);
698                 }
699         }
700 }
701
702 /*
703  * Skip over directory entries that are not on the tape
704  *
705  * First have to get definition of a dirent.
706  */
707 #undef DIRBLKSIZ
708 #include <dirent.h>
709 #undef d_ino
710
711 struct dirent *
712 glob_readdir(void *dirp)
713 {
714         struct direct *dp;
715         static struct dirent adirent;
716
717         while ((dp = rst_readdir(dirp)) != NULL) {
718                 if (!vflag && dp->d_ino == WINO)
719                         continue;
720                 if (dflag || TSTINO(dp->d_ino, dumpmap))
721                         break;
722         }
723         if (dp == NULL)
724                 return (NULL);
725         adirent.d_fileno = dp->d_ino;
726         adirent.d_namlen = dp->d_namlen;
727         memmove(adirent.d_name, dp->d_name, dp->d_namlen + 1);
728         return (&adirent);
729 }
730
731 /*
732  * Return st_mode information in response to stat or lstat calls
733  */
734 static int
735 glob_stat(const char *name, struct stat *stp)
736 {
737         struct direct *dp;
738
739         dp = pathsearch(name);
740         if (dp == NULL || (!dflag && TSTINO(dp->d_ino, dumpmap) == 0) ||
741             (!vflag && dp->d_ino == WINO))
742                 return (-1);
743         if (inodetype(dp->d_ino) == NODE)
744                 stp->st_mode = IFDIR;
745         else
746                 stp->st_mode = IFREG;
747         return (0);
748 }
749
750 /*
751  * Comparison routine for qsort.
752  */
753 static int
754 fcmp(const void *f1, const void *f2)
755 {
756         return (strcoll(((struct afile *)f1)->fname,
757             ((struct afile *)f2)->fname));
758 }
759
760 /*
761  * respond to interrupts
762  */
763 void
764 onintr(int signo __unused)
765 {
766         if (command == 'i' && runshell)
767                 longjmp(reset, 1);
768         if (reply("restore interrupted, continue") == FAIL)
769                 done(1);
770 }