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