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