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