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