1 /* 2 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 * Copyright (c) 2016 by Delphix. All rights reserved. 5 */ 6 7 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ 8 /* All Rights Reserved */ 9 10 /* 11 * Copyright (c) 1980 Regents of the University of California. 12 * All rights reserved. The Berkeley Software License Agreement 13 * specifies the terms and conditions for redistribution. 14 */ 15 16 #pragma ident "%Z%%M% %I% %E% SMI" 17 18 #include "sh.h" 19 #include "sh.dir.h" 20 #include "sh.tconst.h" 21 22 /* 23 * C Shell - directory management 24 */ 25 26 struct directory *dfind(tchar *); 27 tchar *dfollow(tchar *); 28 tchar *dcanon(tchar *, tchar *); 29 void dtildepr(tchar *, tchar *); 30 void dfree(struct directory *); 31 void dnewcwd(struct directory *); 32 33 struct directory dhead; /* "head" of loop */ 34 int printd; /* force name to be printed */ 35 static tchar *fakev[] = { S_dirs, NOSTR }; 36 37 /* 38 * dinit - initialize current working directory 39 */ 40 void 41 dinit(tchar *hp) 42 { 43 tchar *cp; 44 struct directory *dp; 45 tchar path[MAXPATHLEN]; 46 47 #ifdef TRACE 48 tprintf("TRACE- dinit()\n"); 49 #endif 50 /* 51 * If this is a login shell, we should have a home directory. But, 52 * if we got here via 'su - <user>' where the user has no directory 53 * in their passwd file, then su has passed HOME=<nothing>, so hp is 54 * non-null, but has zero length. Thus, we do not know the current 55 * working directory based on the home directory. 56 */ 57 if (loginsh && hp && *hp) 58 cp = hp; 59 else { 60 cp = getwd_(path); 61 if (cp == NULL) { 62 printf("Warning: cannot determine current directory\n"); 63 cp = S_DOT; 64 } 65 } 66 dp = (struct directory *)xcalloc(sizeof (struct directory), 1); 67 dp->di_name = savestr(cp); 68 dp->di_count = 0; 69 dhead.di_next = dhead.di_prev = dp; 70 dp->di_next = dp->di_prev = &dhead; 71 printd = 0; 72 dnewcwd(dp); 73 } 74 75 /* 76 * dodirs - list all directories in directory loop 77 */ 78 void 79 dodirs(tchar **v) 80 { 81 struct directory *dp; 82 bool lflag; 83 tchar *hp = value(S_home); 84 85 #ifdef TRACE 86 tprintf("TRACE- dodirs()\n"); 87 #endif 88 if (*hp == '\0') 89 hp = NOSTR; 90 if (*++v != NOSTR) 91 if (eq(*v, S_MINl /* "-l" */) && *++v == NOSTR) 92 lflag = 1; 93 else 94 error("Usage: dirs [ -l ]"); 95 else 96 lflag = 0; 97 dp = dcwd; 98 do { 99 if (dp == &dhead) 100 continue; 101 if (!lflag && hp != NOSTR) { 102 dtildepr(hp, dp->di_name); 103 } else 104 printf("%t", dp->di_name); 105 printf(" "); 106 } while ((dp = dp->di_prev) != dcwd); 107 printf("\n"); 108 } 109 110 void 111 dtildepr(tchar *home, tchar *dir) 112 { 113 114 #ifdef TRACE 115 tprintf("TRACE- dtildepr()\n"); 116 #endif 117 if (!eq(home, S_SLASH /* "/" */) && prefix(home, dir)) 118 printf("~%t", dir + strlen_(home)); 119 else 120 printf("%t", dir); 121 } 122 123 /* 124 * dochngd - implement chdir command. 125 */ 126 void 127 dochngd(tchar **v) 128 { 129 tchar *cp; 130 struct directory *dp; 131 132 #ifdef TRACE 133 tprintf("TRACE- dochngd()\n"); 134 #endif 135 printd = 0; 136 if (*++v == NOSTR) { 137 if ((cp = value(S_home)) == NOSTR || *cp == 0) 138 bferr("No home directory"); 139 if (chdir_(cp) < 0) 140 bferr("Can't change to home directory"); 141 cp = savestr(cp); 142 } else if ((dp = dfind(*v)) != 0) { 143 printd = 1; 144 if (chdir_(dp->di_name) < 0) 145 Perror(dp->di_name); 146 dcwd->di_prev->di_next = dcwd->di_next; 147 dcwd->di_next->di_prev = dcwd->di_prev; 148 goto flushcwd; 149 } else 150 cp = dfollow(*v); 151 dp = (struct directory *)xcalloc(sizeof (struct directory), 1); 152 dp->di_name = cp; 153 dp->di_count = 0; 154 dp->di_next = dcwd->di_next; 155 dp->di_prev = dcwd->di_prev; 156 dp->di_prev->di_next = dp; 157 dp->di_next->di_prev = dp; 158 flushcwd: 159 dfree(dcwd); 160 dnewcwd(dp); 161 } 162 163 /* 164 * dfollow - change to arg directory; fall back on cdpath if not valid 165 */ 166 tchar * 167 dfollow(tchar *cp) 168 { 169 tchar *dp; 170 struct varent *c; 171 int cdhashval, cdhashval1; 172 int index; 173 int slash; /* slashes in the argument */ 174 tchar *fullpath; 175 tchar *slashcp; /* cp string prepended with a slash */ 176 177 #ifdef TRACE 178 tprintf("TRACE- dfollow()\n"); 179 #endif 180 cp = globone(cp); 181 if (chdir_(cp) >= 0) 182 goto gotcha; 183 184 /* 185 * If the directory argument has a slash in it, 186 * for example, directory/directory, then can't 187 * find that in the cache table. 188 */ 189 slash = any('/', cp); 190 191 /* 192 * Try interpreting wrt successive components of cdpath. 193 * cdpath caching is turned off or directory argument 194 * has a slash in it. 195 */ 196 if (cp[0] != '/' 197 && !prefix(S_DOTSLA /* "./" */, cp) 198 && !prefix(S_DOTDOTSLA /* "../" */, cp) 199 && (c = adrof(S_cdpath)) 200 && (!havhash2 || slash)) { 201 tchar **cdp; 202 tchar *p; 203 tchar buf[MAXPATHLEN]; 204 205 for (cdp = c->vec; *cdp; cdp++) { 206 for (dp = buf, p = *cdp; *dp++ = *p++; ) 207 ; 208 dp[-1] = '/'; 209 for (p = cp; *dp++ = *p++; ) 210 ; 211 if (chdir_(buf) >= 0) { 212 printd = 1; 213 xfree(cp); 214 cp = savestr(buf); 215 goto gotcha; 216 } 217 } 218 } 219 220 /* cdpath caching turned on */ 221 if (cp[0] != '/' 222 && !prefix(S_DOTSLA /* "./" */, cp) 223 && !prefix(S_DOTDOTSLA /* "../" */, cp) 224 && (c = adrof(S_cdpath)) 225 && havhash2 && !slash) { 226 tchar **pv; 227 228 /* If no cdpath or no paths in cdpath, leave */ 229 if (c == 0 || c->vec[0] == 0) 230 pv = justabs; 231 else 232 pv = c->vec; 233 234 slashcp = strspl(S_SLASH, cp); 235 236 cdhashval = hashname(cp); 237 238 /* index points to next path component to test */ 239 index = 0; 240 241 /* 242 * Look at each path in cdpath until get a match. 243 * Only look at those path beginning with a slash 244 */ 245 do { 246 /* only check cache for absolute pathnames */ 247 if (pv[0][0] == '/') { 248 cdhashval1 = hash(cdhashval, index); 249 if (bit(xhash2, cdhashval1)) { 250 /* 251 * concatenate found path with 252 * arg directory 253 */ 254 fullpath = strspl(*pv, slashcp); 255 if (chdir_(fullpath) >= 0) { 256 printd = 1; 257 xfree(cp); 258 cp = savestr(fullpath); 259 xfree(slashcp); 260 xfree(fullpath); 261 goto gotcha; 262 } 263 } 264 } 265 /* 266 * relative pathnames are not cached, and must be 267 * checked manually 268 */ 269 else { 270 tchar *p; 271 tchar buf[MAXPATHLEN]; 272 273 for (dp = buf, p = *pv; *dp++ = *p++; ) 274 ; 275 dp[-1] = '/'; 276 for (p = cp; *dp++ = *p++; ) 277 ; 278 if (chdir_(buf) >= 0) { 279 printd = 1; 280 xfree(cp); 281 cp = savestr(buf); 282 xfree(slashcp); 283 goto gotcha; 284 } 285 } 286 pv++; 287 index++; 288 } while (*pv); 289 } 290 291 /* 292 * Try dereferencing the variable named by the argument. 293 */ 294 dp = value(cp); 295 if ((dp[0] == '/' || dp[0] == '.') && chdir_(dp) >= 0) { 296 xfree(cp); 297 cp = savestr(dp); 298 printd = 1; 299 goto gotcha; 300 } 301 xfree(cp); /* XXX, use after free */ 302 Perror(cp); 303 304 gotcha: 305 if (*cp != '/') { 306 tchar *p, *q; 307 int cwdlen; 308 int len; 309 310 /* 311 * All in the name of efficiency? 312 */ 313 314 if ((cwdlen = (strlen_(dcwd->di_name))) == 1) { 315 if (*dcwd->di_name == '/') /* root */ 316 cwdlen = 0; 317 else 318 { 319 /* 320 * if we are here, when the shell started 321 * it was unable to getwd(), lets try it again 322 */ 323 tchar path[MAXPATHLEN]; 324 325 p = getwd_(path); 326 if (p == NULL) 327 error("cannot determine current directory"); 328 else 329 { 330 xfree(dcwd->di_name); 331 dcwd->di_name = savestr(p); 332 xfree(cp); 333 cp = savestr(p); 334 return dcanon(cp, cp); 335 } 336 337 } 338 } 339 /* 340 * 341 * for (p = cp; *p++;) 342 * ; 343 * dp = (tchar *)xalloc((unsigned) (cwdlen + (p - cp) + 1)*sizeof (tchar)) 344 */ 345 len = strlen_(cp); 346 dp = (tchar *)xalloc((unsigned)(cwdlen + len + 2) * sizeof (tchar)); 347 for (p = dp, q = dcwd->di_name; *p++ = *q++; ) 348 ; 349 if (cwdlen) 350 p[-1] = '/'; 351 else 352 p--; /* don't add a / after root */ 353 for (q = cp; *p++ = *q++; ) 354 ; 355 xfree(cp); 356 cp = dp; 357 dp += cwdlen; 358 } else 359 dp = cp; 360 return dcanon(cp, dp); 361 } 362 363 /* 364 * dopushd - push new directory onto directory stack. 365 * with no arguments exchange top and second. 366 * with numeric argument (+n) bring it to top. 367 */ 368 void 369 dopushd(tchar **v) 370 { 371 struct directory *dp; 372 373 #ifdef TRACE 374 tprintf("TRACE- dopushd()\n"); 375 #endif 376 printd = 1; 377 if (*++v == NOSTR) { 378 if ((dp = dcwd->di_prev) == &dhead) 379 dp = dhead.di_prev; 380 if (dp == dcwd) 381 bferr("No other directory"); 382 if (chdir_(dp->di_name) < 0) 383 Perror(dp->di_name); 384 dp->di_prev->di_next = dp->di_next; 385 dp->di_next->di_prev = dp->di_prev; 386 dp->di_next = dcwd->di_next; 387 dp->di_prev = dcwd; 388 dcwd->di_next->di_prev = dp; 389 dcwd->di_next = dp; 390 } else if (dp = dfind(*v)) { 391 if (chdir_(dp->di_name) < 0) 392 Perror(dp->di_name); 393 } else { 394 tchar *cp; 395 396 cp = dfollow(*v); 397 dp = (struct directory *)xcalloc(sizeof (struct directory), 1); 398 dp->di_name = cp; 399 dp->di_count = 0; 400 dp->di_prev = dcwd; 401 dp->di_next = dcwd->di_next; 402 dcwd->di_next = dp; 403 dp->di_next->di_prev = dp; 404 } 405 dnewcwd(dp); 406 } 407 408 /* 409 * dfind - find a directory if specified by numeric (+n) argument 410 */ 411 struct directory * 412 dfind(tchar *cp) 413 { 414 struct directory *dp; 415 int i; 416 tchar *ep; 417 418 #ifdef TRACE 419 tprintf("TRACE- dfind()\n"); 420 #endif 421 if (*cp++ != '+') 422 return (0); 423 for (ep = cp; digit(*ep); ep++) 424 continue; 425 if (*ep) 426 return (0); 427 i = getn(cp); 428 if (i <= 0) 429 return (0); 430 for (dp = dcwd; i != 0; i--) { 431 if ((dp = dp->di_prev) == &dhead) 432 dp = dp->di_prev; 433 if (dp == dcwd) 434 bferr("Directory stack not that deep"); 435 } 436 return (dp); 437 } 438 439 /* 440 * dopopd - pop a directory out of the directory stack 441 * with a numeric argument just discard it. 442 */ 443 void 444 dopopd(tchar **v) 445 { 446 struct directory *dp, *p; 447 448 #ifdef TRACE 449 tprintf("TRACE- dopopd()\n"); 450 #endif 451 printd = 1; 452 if (*++v == NOSTR) 453 dp = dcwd; 454 else if ((dp = dfind(*v)) == 0) 455 bferr("Invalid argument"); 456 if (dp->di_prev == &dhead && dp->di_next == &dhead) 457 bferr("Directory stack empty"); 458 if (dp == dcwd) { 459 if ((p = dp->di_prev) == &dhead) 460 p = dhead.di_prev; 461 if (chdir_(p->di_name) < 0) 462 Perror(p->di_name); 463 } 464 dp->di_prev->di_next = dp->di_next; 465 dp->di_next->di_prev = dp->di_prev; 466 if (dp == dcwd) 467 dnewcwd(p); 468 else 469 dodirs(fakev); 470 dfree(dp); 471 } 472 473 /* 474 * dfree - free the directory (or keep it if it still has ref count) 475 */ 476 void 477 dfree(struct directory *dp) 478 { 479 480 #ifdef TRACE 481 tprintf("TRACE- dfree()\n"); 482 #endif 483 if (dp->di_count != 0) 484 dp->di_next = dp->di_prev = 0; 485 else 486 xfree(dp->di_name), xfree((tchar *)dp); 487 } 488 489 /* 490 * dcanon - canonicalize the pathname, removing excess ./ and ../ etc. 491 * We are of course assuming that the file system is standardly 492 * constructed (always have ..'s, directories have links). 493 * 494 * If the hardpaths shell variable is set, resolve the 495 * resulting pathname to contain no symbolic link components. 496 */ 497 tchar * 498 dcanon(tchar *cp, tchar *p) 499 { 500 tchar *sp; /* rightmost component currently under 501 consideration */ 502 tchar *p1, /* general purpose */ 503 *p2; 504 bool slash, dotdot, hardpaths; 505 506 #ifdef TRACE 507 tprintf("TRACE- dcannon()\n"); 508 #endif 509 510 if (*cp != '/') 511 abort(); 512 513 if (hardpaths = (adrof(S_hardpaths) != NULL)) { 514 /* 515 * Be paranoid: don't trust the initial prefix 516 * to be symlink-free. 517 */ 518 p = cp; 519 } 520 521 /* 522 * Loop invariant: cp points to the overall path start, 523 * p to its as yet uncanonicalized trailing suffix. 524 */ 525 while (*p) { /* for each component */ 526 sp = p; /* save slash address */ 527 528 while (*++p == '/') /* flush extra slashes */ 529 ; 530 if (p != ++sp) 531 for (p1 = sp, p2 = p; *p1++ = *p2++; ) 532 ; 533 534 p = sp; /* save start of component */ 535 slash = 0; 536 if (*p) 537 while (*++p) /* find next slash or end of path */ 538 if (*p == '/') { 539 slash = 1; 540 *p = '\0'; 541 break; 542 } 543 544 if (*sp == '\0') { 545 /* component is null */ 546 if (--sp == cp) /* if path is one tchar (i.e. /) */ 547 break; 548 else 549 *sp = '\0'; 550 continue; 551 } 552 553 if (sp[0] == '.' && sp[1] == '\0') { 554 /* Squeeze out component consisting of "." */ 555 if (slash) { 556 for (p1 = sp, p2 = p + 1; *p1++ = *p2++; ) 557 ; 558 p = --sp; 559 } else if (--sp != cp) 560 *sp = '\0'; 561 continue; 562 } 563 564 /* 565 * At this point we have a path of the form "x/yz", 566 * where "x" is null or rooted at "/", "y" is a single 567 * component, and "z" is possibly null. The pointer cp 568 * points to the start of "x", sp to the start of "y", 569 * and p to the beginning of "z", which has been forced 570 * to a null. 571 */ 572 /* 573 * Process symbolic link component. Provided that either 574 * the hardpaths shell variable is set or "y" is really 575 * ".." we replace the symlink with its contents. The 576 * second condition for replacement is necessary to make 577 * the command "cd x/.." produce the same results as the 578 * sequence "cd x; cd ..". 579 * 580 * Note that the two conditions correspond to different 581 * potential symlinks. When hardpaths is set, we must 582 * check "x/y"; otherwise, when "y" is known to be "..", 583 * we check "x". 584 */ 585 dotdot = sp[0] == '.' && sp[1] == '.' && sp[2] == '\0'; 586 if (hardpaths || dotdot) { 587 tchar link[MAXPATHLEN]; 588 int cc; 589 tchar *newcp; 590 591 /* 592 * Isolate the end of the component that is to 593 * be checked for symlink-hood. 594 */ 595 sp--; 596 if (! hardpaths) 597 *sp = '\0'; 598 599 /* 600 * See whether the component is really a symlink by 601 * trying to read it. If the read succeeds, it is. 602 */ 603 if ((hardpaths || sp > cp) && 604 (cc = readlink_(cp, link, MAXPATHLEN)) >= 0) { 605 /* 606 * readlink_ put null, so we don't need this. 607 */ 608 /* link[cc] = '\0'; */ 609 610 /* Restore path. */ 611 if (slash) 612 *p = '/'; 613 614 /* 615 * Point p at the start of the trailing 616 * path following the symlink component. 617 * It's already there is hardpaths is set. 618 */ 619 if (! hardpaths) { 620 /* Restore path as well. */ 621 *(p = sp) = '/'; 622 } 623 624 /* 625 * Find length of p. 626 */ 627 for (p1 = p; *p1++; ) 628 ; 629 630 if (*link != '/') { 631 /* 632 * Relative path: replace the symlink 633 * component with its value. First, 634 * set sp to point to the slash at 635 * its beginning. If hardpaths is 636 * set, this is already the case. 637 */ 638 if (! hardpaths) { 639 while (*--sp != '/') 640 ; 641 } 642 643 /* 644 * Terminate the leading part of the 645 * path, including trailing slash. 646 */ 647 sp++; 648 *sp = '\0'; 649 650 /* 651 * New length is: "x/" + link + "z" 652 */ 653 p1 = newcp = (tchar *)xalloc((unsigned) 654 ((sp - cp) + cc + (p1 - p)) * sizeof (tchar)); 655 /* 656 * Copy new path into newcp 657 */ 658 for (p2 = cp; *p1++ = *p2++; ) 659 ; 660 for (p1--, p2 = link; *p1++ = *p2++; ) 661 ; 662 for (p1--, p2 = p; *p1++ = *p2++; ) 663 ; 664 /* 665 * Restart canonicalization at 666 * expanded "/y". 667 */ 668 p = sp - cp - 1 + newcp; 669 } else { 670 /* 671 * New length is: link + "z" 672 */ 673 p1 = newcp = (tchar *)xalloc((unsigned) 674 (cc + (p1 - p))*sizeof (tchar)); 675 /* 676 * Copy new path into newcp 677 */ 678 for (p2 = link; *p1++ = *p2++; ) 679 ; 680 for (p1--, p2 = p; *p1++ = *p2++; ) 681 ; 682 /* 683 * Restart canonicalization at beginning 684 */ 685 p = newcp; 686 } 687 xfree(cp); 688 cp = newcp; 689 continue; /* canonicalize the link */ 690 } 691 692 /* The component wasn't a symlink after all. */ 693 if (! hardpaths) 694 *sp = '/'; 695 } 696 697 if (dotdot) { 698 if (sp != cp) 699 while (*--sp != '/') 700 ; 701 if (slash) { 702 for (p1 = sp + 1, p2 = p + 1; *p1++ = *p2++; ) 703 ; 704 p = sp; 705 } else if (cp == sp) 706 *++sp = '\0'; 707 else 708 *sp = '\0'; 709 continue; 710 } 711 712 if (slash) 713 *p = '/'; 714 } 715 return cp; 716 } 717 718 /* 719 * dnewcwd - make a new directory in the loop the current one 720 * and export its name to the PWD environment variable. 721 */ 722 void 723 dnewcwd(struct directory *dp) 724 { 725 726 #ifdef TRACE 727 tprintf("TRACE- dnewcwd()\n"); 728 #endif 729 dcwd = dp; 730 #ifdef notdef 731 /* 732 * If we have a fast version of getwd available 733 * and hardpaths is set, it would be reasonable 734 * here to verify that dcwd->di_name really does 735 * name the current directory. Later... 736 */ 737 #endif /* notdef */ 738 739 didchdir = 1; 740 set(S_cwd, savestr(dcwd->di_name)); 741 didchdir = 0; 742 local_setenv(S_PWD, dcwd->di_name); 743 if (printd) 744 dodirs(fakev); 745 } 746