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