1 /* 2 * Copyright (c) 1985 Regents of the University of California. 3 * All rights reserved. The Berkeley software License Agreement 4 * specifies the terms and conditions for redistribution. 5 */ 6 7 #ifndef lint 8 static char sccsid[] = "@(#)interactive.c 5.5 (Berkeley) 4/23/87"; 9 #endif not lint 10 11 #include "restore.h" 12 #include <protocols/dumprestore.h> 13 #include <setjmp.h> 14 15 #define round(a, b) (((a) + (b) - 1) / (b) * (b)) 16 17 /* 18 * Things to handle interruptions. 19 */ 20 static jmp_buf reset; 21 static char *nextarg = NULL; 22 23 /* 24 * Structure and routines associated with listing directories. 25 */ 26 struct afile { 27 ino_t fnum; /* inode number of file */ 28 char *fname; /* file name */ 29 short fflags; /* extraction flags, if any */ 30 char ftype; /* file type, e.g. LEAF or NODE */ 31 }; 32 struct arglist { 33 struct afile *head; /* start of argument list */ 34 struct afile *last; /* end of argument list */ 35 struct afile *base; /* current list arena */ 36 int nent; /* maximum size of list */ 37 char *cmd; /* the current command */ 38 }; 39 extern int fcmp(); 40 extern char *fmtentry(); 41 char *copynext(); 42 43 /* 44 * Read and execute commands from the terminal. 45 */ 46 runcmdshell() 47 { 48 register struct entry *np; 49 ino_t ino; 50 static struct arglist alist = { 0, 0, 0, 0, 0 }; 51 char curdir[MAXPATHLEN]; 52 char name[MAXPATHLEN]; 53 char cmd[BUFSIZ]; 54 55 canon("/", curdir); 56 loop: 57 if (setjmp(reset) != 0) { 58 for (; alist.head < alist.last; alist.head++) 59 freename(alist.head->fname); 60 nextarg = NULL; 61 volno = 0; 62 } 63 getcmd(curdir, cmd, name, &alist); 64 switch (cmd[0]) { 65 /* 66 * Add elements to the extraction list. 67 */ 68 case 'a': 69 if (strncmp(cmd, "add", strlen(cmd)) != 0) 70 goto bad; 71 ino = dirlookup(name); 72 if (ino == 0) 73 break; 74 if (mflag) 75 pathcheck(name); 76 treescan(name, ino, addfile); 77 break; 78 /* 79 * Change working directory. 80 */ 81 case 'c': 82 if (strncmp(cmd, "cd", strlen(cmd)) != 0) 83 goto bad; 84 ino = dirlookup(name); 85 if (ino == 0) 86 break; 87 if (inodetype(ino) == LEAF) { 88 fprintf(stderr, "%s: not a directory\n", name); 89 break; 90 } 91 (void) strcpy(curdir, name); 92 break; 93 /* 94 * Delete elements from the extraction list. 95 */ 96 case 'd': 97 if (strncmp(cmd, "delete", strlen(cmd)) != 0) 98 goto bad; 99 np = lookupname(name); 100 if (np == NIL || (np->e_flags & NEW) == 0) { 101 fprintf(stderr, "%s: not on extraction list\n", name); 102 break; 103 } 104 treescan(name, np->e_ino, deletefile); 105 break; 106 /* 107 * Extract the requested list. 108 */ 109 case 'e': 110 if (strncmp(cmd, "extract", strlen(cmd)) != 0) 111 goto bad; 112 createfiles(); 113 createlinks(); 114 setdirmodes(); 115 if (dflag) 116 checkrestore(); 117 volno = 0; 118 break; 119 /* 120 * List available commands. 121 */ 122 case 'h': 123 if (strncmp(cmd, "help", strlen(cmd)) != 0) 124 goto bad; 125 case '?': 126 fprintf(stderr, "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", 127 "Available commands are:\n", 128 "\tls [arg] - list directory\n", 129 "\tcd arg - change directory\n", 130 "\tpwd - print current directory\n", 131 "\tadd [arg] - add `arg' to list of", 132 " files to be extracted\n", 133 "\tdelete [arg] - delete `arg' from", 134 " list of files to be extracted\n", 135 "\textract - extract requested files\n", 136 "\tsetmodes - set modes of requested directories\n", 137 "\tquit - immediately exit program\n", 138 "\twhat - list dump header information\n", 139 "\tverbose - toggle verbose flag", 140 " (useful with ``ls'')\n", 141 "\thelp or `?' - print this list\n", 142 "If no `arg' is supplied, the current", 143 " directory is used\n"); 144 break; 145 /* 146 * List a directory. 147 */ 148 case 'l': 149 if (strncmp(cmd, "ls", strlen(cmd)) != 0) 150 goto bad; 151 ino = dirlookup(name); 152 if (ino == 0) 153 break; 154 printlist(name, ino, curdir); 155 break; 156 /* 157 * Print current directory. 158 */ 159 case 'p': 160 if (strncmp(cmd, "pwd", strlen(cmd)) != 0) 161 goto bad; 162 if (curdir[1] == '\0') 163 fprintf(stderr, "/\n"); 164 else 165 fprintf(stderr, "%s\n", &curdir[1]); 166 break; 167 /* 168 * Quit. 169 */ 170 case 'q': 171 if (strncmp(cmd, "quit", strlen(cmd)) != 0) 172 goto bad; 173 return; 174 case 'x': 175 if (strncmp(cmd, "xit", strlen(cmd)) != 0) 176 goto bad; 177 return; 178 /* 179 * Toggle verbose mode. 180 */ 181 case 'v': 182 if (strncmp(cmd, "verbose", strlen(cmd)) != 0) 183 goto bad; 184 if (vflag) { 185 fprintf(stderr, "verbose mode off\n"); 186 vflag = 0; 187 break; 188 } 189 fprintf(stderr, "verbose mode on\n"); 190 vflag++; 191 break; 192 /* 193 * Just restore requested directory modes. 194 */ 195 case 's': 196 if (strncmp(cmd, "setmodes", strlen(cmd)) != 0) 197 goto bad; 198 setdirmodes(); 199 break; 200 /* 201 * Print out dump header information. 202 */ 203 case 'w': 204 if (strncmp(cmd, "what", strlen(cmd)) != 0) 205 goto bad; 206 printdumpinfo(); 207 break; 208 /* 209 * Turn on debugging. 210 */ 211 case 'D': 212 if (strncmp(cmd, "Debug", strlen(cmd)) != 0) 213 goto bad; 214 if (dflag) { 215 fprintf(stderr, "debugging mode off\n"); 216 dflag = 0; 217 break; 218 } 219 fprintf(stderr, "debugging mode on\n"); 220 dflag++; 221 break; 222 /* 223 * Unknown command. 224 */ 225 default: 226 bad: 227 fprintf(stderr, "%s: unknown command; type ? for help\n", cmd); 228 break; 229 } 230 goto loop; 231 } 232 233 /* 234 * Read and parse an interactive command. 235 * The first word on the line is assigned to "cmd". If 236 * there are no arguments on the command line, then "curdir" 237 * is returned as the argument. If there are arguments 238 * on the line they are returned one at a time on each 239 * successive call to getcmd. Each argument is first assigned 240 * to "name". If it does not start with "/" the pathname in 241 * "curdir" is prepended to it. Finally "canon" is called to 242 * eliminate any embedded ".." components. 243 */ 244 getcmd(curdir, cmd, name, ap) 245 char *curdir, *cmd, *name; 246 struct arglist *ap; 247 { 248 register char *cp; 249 static char input[BUFSIZ]; 250 char output[BUFSIZ]; 251 # define rawname input /* save space by reusing input buffer */ 252 253 /* 254 * Check to see if still processing arguments. 255 */ 256 if (ap->head != ap->last) { 257 strcpy(name, ap->head->fname); 258 freename(ap->head->fname); 259 ap->head++; 260 return; 261 } 262 if (nextarg != NULL) 263 goto getnext; 264 /* 265 * Read a command line and trim off trailing white space. 266 */ 267 do { 268 fprintf(stderr, "restore > "); 269 (void) fflush(stderr); 270 (void) fgets(input, BUFSIZ, terminal); 271 } while (!feof(terminal) && input[0] == '\n'); 272 if (feof(terminal)) { 273 (void) strcpy(cmd, "quit"); 274 return; 275 } 276 for (cp = &input[strlen(input) - 2]; *cp == ' ' || *cp == '\t'; cp--) 277 /* trim off trailing white space and newline */; 278 *++cp = '\0'; 279 /* 280 * Copy the command into "cmd". 281 */ 282 cp = copynext(input, cmd); 283 ap->cmd = cmd; 284 /* 285 * If no argument, use curdir as the default. 286 */ 287 if (*cp == '\0') { 288 (void) strcpy(name, curdir); 289 return; 290 } 291 nextarg = cp; 292 /* 293 * Find the next argument. 294 */ 295 getnext: 296 cp = copynext(nextarg, rawname); 297 if (*cp == '\0') 298 nextarg = NULL; 299 else 300 nextarg = cp; 301 /* 302 * If it an absolute pathname, canonicalize it and return it. 303 */ 304 if (rawname[0] == '/') { 305 canon(rawname, name); 306 } else { 307 /* 308 * For relative pathnames, prepend the current directory to 309 * it then canonicalize and return it. 310 */ 311 (void) strcpy(output, curdir); 312 (void) strcat(output, "/"); 313 (void) strcat(output, rawname); 314 canon(output, name); 315 } 316 expandarg(name, ap); 317 strcpy(name, ap->head->fname); 318 freename(ap->head->fname); 319 ap->head++; 320 # undef rawname 321 } 322 323 /* 324 * Strip off the next token of the input. 325 */ 326 char * 327 copynext(input, output) 328 char *input, *output; 329 { 330 register char *cp, *bp; 331 char quote; 332 333 for (cp = input; *cp == ' ' || *cp == '\t'; cp++) 334 /* skip to argument */; 335 bp = output; 336 while (*cp != ' ' && *cp != '\t' && *cp != '\0') { 337 /* 338 * Handle back slashes. 339 */ 340 if (*cp == '\\') { 341 if (*++cp == '\0') { 342 fprintf(stderr, 343 "command lines cannot be continued\n"); 344 continue; 345 } 346 *bp++ = *cp++; 347 continue; 348 } 349 /* 350 * The usual unquoted case. 351 */ 352 if (*cp != '\'' && *cp != '"') { 353 *bp++ = *cp++; 354 continue; 355 } 356 /* 357 * Handle single and double quotes. 358 */ 359 quote = *cp++; 360 while (*cp != quote && *cp != '\0') 361 *bp++ = *cp++ | 0200; 362 if (*cp++ == '\0') { 363 fprintf(stderr, "missing %c\n", quote); 364 cp--; 365 continue; 366 } 367 } 368 *bp = '\0'; 369 return (cp); 370 } 371 372 /* 373 * Canonicalize file names to always start with ``./'' and 374 * remove any imbedded "." and ".." components. 375 */ 376 canon(rawname, canonname) 377 char *rawname, *canonname; 378 { 379 register char *cp, *np; 380 int len; 381 382 if (strcmp(rawname, ".") == 0 || strncmp(rawname, "./", 2) == 0) 383 (void) strcpy(canonname, ""); 384 else if (rawname[0] == '/') 385 (void) strcpy(canonname, "."); 386 else 387 (void) strcpy(canonname, "./"); 388 (void) strcat(canonname, rawname); 389 /* 390 * Eliminate multiple and trailing '/'s 391 */ 392 for (cp = np = canonname; *np != '\0'; cp++) { 393 *cp = *np++; 394 while (*cp == '/' && *np == '/') 395 np++; 396 } 397 *cp = '\0'; 398 if (*--cp == '/') 399 *cp = '\0'; 400 /* 401 * Eliminate extraneous "." and ".." from pathnames. 402 */ 403 for (np = canonname; *np != '\0'; ) { 404 np++; 405 cp = np; 406 while (*np != '/' && *np != '\0') 407 np++; 408 if (np - cp == 1 && *cp == '.') { 409 cp--; 410 (void) strcpy(cp, np); 411 np = cp; 412 } 413 if (np - cp == 2 && strncmp(cp, "..", 2) == 0) { 414 cp--; 415 while (cp > &canonname[1] && *--cp != '/') 416 /* find beginning of name */; 417 (void) strcpy(cp, np); 418 np = cp; 419 } 420 } 421 } 422 423 /* 424 * globals (file name generation) 425 * 426 * "*" in params matches r.e ".*" 427 * "?" in params matches r.e. "." 428 * "[...]" in params matches character class 429 * "[...a-z...]" in params matches a through z. 430 */ 431 expandarg(arg, ap) 432 char *arg; 433 register struct arglist *ap; 434 { 435 static struct afile single; 436 struct entry *ep; 437 int size; 438 439 ap->head = ap->last = (struct afile *)0; 440 size = expand(arg, 0, ap); 441 if (size == 0) { 442 ep = lookupname(arg); 443 single.fnum = ep ? ep->e_ino : 0; 444 single.fname = savename(arg); 445 ap->head = &single; 446 ap->last = ap->head + 1; 447 return; 448 } 449 qsort((char *)ap->head, ap->last - ap->head, sizeof *ap->head, fcmp); 450 } 451 452 /* 453 * Expand a file name 454 */ 455 expand(as, rflg, ap) 456 char *as; 457 int rflg; 458 register struct arglist *ap; 459 { 460 int count, size; 461 char dir = 0; 462 char *rescan = 0; 463 DIR *dirp; 464 register char *s, *cs; 465 int sindex, rindex, lindex; 466 struct direct *dp; 467 register char slash; 468 register char *rs; 469 register char c; 470 471 /* 472 * check for meta chars 473 */ 474 s = cs = as; 475 slash = 0; 476 while (*cs != '*' && *cs != '?' && *cs != '[') { 477 if (*cs++ == 0) { 478 if (rflg && slash) 479 break; 480 else 481 return (0) ; 482 } else if (*cs == '/') { 483 slash++; 484 } 485 } 486 for (;;) { 487 if (cs == s) { 488 s = ""; 489 break; 490 } else if (*--cs == '/') { 491 *cs = 0; 492 if (s == cs) 493 s = "/"; 494 break; 495 } 496 } 497 if ((dirp = rst_opendir(s)) != NULL) 498 dir++; 499 count = 0; 500 if (*cs == 0) 501 *cs++ = 0200; 502 if (dir) { 503 /* 504 * check for rescan 505 */ 506 rs = cs; 507 do { 508 if (*rs == '/') { 509 rescan = rs; 510 *rs = 0; 511 } 512 } while (*rs++); 513 sindex = ap->last - ap->head; 514 while ((dp = rst_readdir(dirp)) != NULL && dp->d_ino != 0) { 515 if (!dflag && BIT(dp->d_ino, dumpmap) == 0) 516 continue; 517 if ((*dp->d_name == '.' && *cs != '.')) 518 continue; 519 if (gmatch(dp->d_name, cs)) { 520 if (addg(dp, s, rescan, ap) < 0) 521 return (-1); 522 count++; 523 } 524 } 525 if (rescan) { 526 rindex = sindex; 527 lindex = ap->last - ap->head; 528 if (count) { 529 count = 0; 530 while (rindex < lindex) { 531 size = expand(ap->head[rindex].fname, 532 1, ap); 533 if (size < 0) 534 return (size); 535 count += size; 536 rindex++; 537 } 538 } 539 bcopy((char *)&ap->head[lindex], 540 (char *)&ap->head[sindex], 541 (ap->last - &ap->head[rindex]) * sizeof *ap->head); 542 ap->last -= lindex - sindex; 543 *rescan = '/'; 544 } 545 } 546 s = as; 547 while (c = *s) 548 *s++ = (c&0177 ? c : '/'); 549 return (count); 550 } 551 552 /* 553 * Check for a name match 554 */ 555 gmatch(s, p) 556 register char *s, *p; 557 { 558 register int scc; 559 char c; 560 char ok; 561 int lc; 562 563 if (scc = *s++) 564 if ((scc &= 0177) == 0) 565 scc = 0200; 566 switch (c = *p++) { 567 568 case '[': 569 ok = 0; 570 lc = 077777; 571 while (c = *p++) { 572 if (c == ']') { 573 return (ok ? gmatch(s, p) : 0); 574 } else if (c == '-') { 575 if (lc <= scc && scc <= (*p++)) 576 ok++ ; 577 } else { 578 if (scc == (lc = (c&0177))) 579 ok++ ; 580 } 581 } 582 return (0); 583 584 default: 585 if ((c&0177) != scc) 586 return (0) ; 587 /* falls through */ 588 589 case '?': 590 return (scc ? gmatch(s, p) : 0); 591 592 case '*': 593 if (*p == 0) 594 return (1) ; 595 s--; 596 while (*s) { 597 if (gmatch(s++, p)) 598 return (1); 599 } 600 return (0); 601 602 case 0: 603 return (scc == 0); 604 } 605 } 606 607 /* 608 * Construct a matched name. 609 */ 610 addg(dp, as1, as3, ap) 611 struct direct *dp; 612 char *as1, *as3; 613 struct arglist *ap; 614 { 615 register char *s1, *s2; 616 register int c; 617 char buf[BUFSIZ]; 618 619 s2 = buf; 620 s1 = as1; 621 while (c = *s1++) { 622 if ((c &= 0177) == 0) { 623 *s2++ = '/'; 624 break; 625 } 626 *s2++ = c; 627 } 628 s1 = dp->d_name; 629 while (*s2 = *s1++) 630 s2++; 631 if (s1 = as3) { 632 *s2++ = '/'; 633 while (*s2++ = *++s1) 634 /* void */; 635 } 636 if (mkentry(buf, dp->d_ino, ap) == FAIL) 637 return (-1); 638 } 639 640 /* 641 * Do an "ls" style listing of a directory 642 */ 643 printlist(name, ino, basename) 644 char *name; 645 ino_t ino; 646 char *basename; 647 { 648 register struct afile *fp; 649 register struct direct *dp; 650 static struct arglist alist = { 0, 0, 0, 0, "ls" }; 651 struct afile single; 652 DIR *dirp; 653 654 if ((dirp = rst_opendir(name)) == NULL) { 655 single.fnum = ino; 656 single.fname = savename(name + strlen(basename) + 1); 657 alist.head = &single; 658 alist.last = alist.head + 1; 659 } else { 660 alist.head = (struct afile *)0; 661 fprintf(stderr, "%s:\n", name); 662 while (dp = rst_readdir(dirp)) { 663 if (dp == NULL || dp->d_ino == 0) 664 break; 665 if (!dflag && BIT(dp->d_ino, dumpmap) == 0) 666 continue; 667 if (vflag == 0 && 668 (strcmp(dp->d_name, ".") == 0 || 669 strcmp(dp->d_name, "..") == 0)) 670 continue; 671 if (!mkentry(dp->d_name, dp->d_ino, &alist)) 672 return; 673 } 674 } 675 if (alist.head != 0) { 676 qsort((char *)alist.head, alist.last - alist.head, 677 sizeof *alist.head, fcmp); 678 formatf(&alist); 679 for (fp = alist.head; fp < alist.last; fp++) 680 freename(fp->fname); 681 } 682 if (dirp != NULL) 683 fprintf(stderr, "\n"); 684 } 685 686 /* 687 * Read the contents of a directory. 688 */ 689 mkentry(name, ino, ap) 690 char *name; 691 ino_t ino; 692 register struct arglist *ap; 693 { 694 register struct afile *fp; 695 696 if (ap->base == NULL) { 697 ap->nent = 20; 698 ap->base = (struct afile *)calloc((unsigned)ap->nent, 699 sizeof (struct afile)); 700 if (ap->base == NULL) { 701 fprintf(stderr, "%s: out of memory\n", ap->cmd); 702 return (FAIL); 703 } 704 } 705 if (ap->head == 0) 706 ap->head = ap->last = ap->base; 707 fp = ap->last; 708 fp->fnum = ino; 709 fp->fname = savename(name); 710 fp++; 711 if (fp == ap->head + ap->nent) { 712 ap->base = (struct afile *)realloc((char *)ap->base, 713 (unsigned)(2 * ap->nent * sizeof (struct afile))); 714 if (ap->base == 0) { 715 fprintf(stderr, "%s: out of memory\n", ap->cmd); 716 return (FAIL); 717 } 718 ap->head = ap->base; 719 fp = ap->head + ap->nent; 720 ap->nent *= 2; 721 } 722 ap->last = fp; 723 return (GOOD); 724 } 725 726 /* 727 * Print out a pretty listing of a directory 728 */ 729 formatf(ap) 730 register struct arglist *ap; 731 { 732 register struct afile *fp; 733 struct entry *np; 734 int width = 0, w, nentry = ap->last - ap->head; 735 int i, j, len, columns, lines; 736 char *cp; 737 738 if (ap->head == ap->last) 739 return; 740 for (fp = ap->head; fp < ap->last; fp++) { 741 fp->ftype = inodetype(fp->fnum); 742 np = lookupino(fp->fnum); 743 if (np != NIL) 744 fp->fflags = np->e_flags; 745 else 746 fp->fflags = 0; 747 len = strlen(fmtentry(fp)); 748 if (len > width) 749 width = len; 750 } 751 width += 2; 752 columns = 80 / width; 753 if (columns == 0) 754 columns = 1; 755 lines = (nentry + columns - 1) / columns; 756 for (i = 0; i < lines; i++) { 757 for (j = 0; j < columns; j++) { 758 fp = ap->head + j * lines + i; 759 cp = fmtentry(fp); 760 fprintf(stderr, "%s", cp); 761 if (fp + lines >= ap->last) { 762 fprintf(stderr, "\n"); 763 break; 764 } 765 w = strlen(cp); 766 while (w < width) { 767 w++; 768 fprintf(stderr, " "); 769 } 770 } 771 } 772 } 773 774 /* 775 * Comparison routine for qsort. 776 */ 777 fcmp(f1, f2) 778 register struct afile *f1, *f2; 779 { 780 781 return (strcmp(f1->fname, f2->fname)); 782 } 783 784 /* 785 * Format a directory entry. 786 */ 787 char * 788 fmtentry(fp) 789 register struct afile *fp; 790 { 791 static char fmtres[BUFSIZ]; 792 static int precision = 0; 793 int i; 794 register char *cp, *dp; 795 796 if (!vflag) { 797 fmtres[0] = '\0'; 798 } else { 799 if (precision == 0) 800 for (i = maxino; i > 0; i /= 10) 801 precision++; 802 (void) sprintf(fmtres, "%*d ", precision, fp->fnum); 803 } 804 dp = &fmtres[strlen(fmtres)]; 805 if (dflag && BIT(fp->fnum, dumpmap) == 0) 806 *dp++ = '^'; 807 else if ((fp->fflags & NEW) != 0) 808 *dp++ = '*'; 809 else 810 *dp++ = ' '; 811 for (cp = fp->fname; *cp; cp++) 812 if (!vflag && (*cp < ' ' || *cp >= 0177)) 813 *dp++ = '?'; 814 else 815 *dp++ = *cp; 816 if (fp->ftype == NODE) 817 *dp++ = '/'; 818 *dp++ = 0; 819 return (fmtres); 820 } 821 822 /* 823 * respond to interrupts 824 */ 825 onintr() 826 { 827 if (command == 'i') 828 longjmp(reset, 1); 829 if (reply("restore interrupted, continue") == FAIL) 830 done(1); 831 } 832