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