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