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