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