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