1 /* 2 * sh.dir.c: Directory manipulation functions 3 */ 4 /*- 5 * Copyright (c) 1980, 1991 The Regents of the University of California. 6 * 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 #include "sh.h" 33 #include "ed.h" 34 35 /* 36 * C Shell - directory management 37 */ 38 39 static Char *agetcwd (void); 40 static void dstart (const char *); 41 static struct directory *dfind (Char *); 42 static Char *dfollow (Char *, int); 43 static void printdirs (int); 44 static Char *dgoto (Char *); 45 static void dnewcwd (struct directory *, int); 46 static void dset (Char *); 47 static void dextract (struct directory *); 48 static int skipargs (Char ***, const char *, 49 const char *); 50 static void dgetstack (void); 51 52 static struct directory dhead INIT_ZERO_STRUCT; /* "head" of loop */ 53 static int printd; /* force name to be printed */ 54 55 int bequiet = 0; /* do not print dir stack -strike */ 56 57 static Char * 58 agetcwd(void) 59 { 60 char *buf; 61 Char *cwd; 62 size_t len; 63 64 len = MAXPATHLEN; 65 buf = xmalloc(len); 66 while (getcwd(buf, len) == NULL) { 67 int err; 68 69 err = errno; 70 if (err != ERANGE) { 71 xfree(buf); 72 errno = err; 73 return NULL; 74 } 75 len *= 2; 76 buf = xrealloc(buf, len); 77 } 78 if (*buf == '\0') { 79 xfree(buf); 80 return NULL; 81 } 82 cwd = SAVE(buf); 83 xfree(buf); 84 return cwd; 85 } 86 87 static void 88 dstart(const char *from) 89 { 90 xprintf(CGETS(12, 1, "%s: Trying to start from \"%s\"\n"), progname, from); 91 } 92 93 /* 94 * dinit - initialize current working directory 95 */ 96 void 97 dinit(Char *hp) 98 { 99 Char *cp, *tcp; 100 struct directory *dp; 101 102 /* Don't believe the login shell home, because it may be a symlink */ 103 tcp = agetcwd(); 104 if (tcp == NULL) { 105 xprintf("%s: %s\n", progname, strerror(errno)); 106 if (hp && *hp) { 107 char *xcp = short2str(hp); 108 dstart(xcp); 109 if (chdir(xcp) == -1) 110 cp = NULL; 111 else 112 cp = Strsave(hp); 113 } 114 else 115 cp = NULL; 116 if (cp == NULL) { 117 dstart("/"); 118 if (chdir("/") == -1) 119 /* I am not even try to print an error message! */ 120 xexit(1); 121 cp = SAVE("/"); 122 } 123 } 124 else { 125 #ifdef S_IFLNK 126 struct stat swd, shp; 127 int swd_ok; 128 129 swd_ok = stat(short2str(tcp), &swd) == 0; 130 /* 131 * See if $HOME is the working directory we got and use that 132 */ 133 if (swd_ok && hp && *hp && stat(short2str(hp), &shp) != -1 && 134 DEV_DEV_COMPARE(swd.st_dev, shp.st_dev) && 135 swd.st_ino == shp.st_ino) 136 cp = Strsave(hp); 137 else { 138 char *cwd; 139 140 /* 141 * use PWD if we have it (for subshells) 142 */ 143 if (swd_ok && (cwd = getenv("PWD")) != NULL) { 144 if (stat(cwd, &shp) != -1 && 145 DEV_DEV_COMPARE(swd.st_dev, shp.st_dev) && 146 swd.st_ino == shp.st_ino) { 147 tcp = SAVE(cwd); 148 cleanup_push(tcp, xfree); 149 } 150 } 151 cleanup_push(tcp, xfree); 152 cp = dcanon(tcp, STRNULL); 153 cleanup_ignore(tcp); 154 cleanup_until(tcp); 155 } 156 #else /* S_IFLNK */ 157 cleanup_push(tcp, xfree); 158 cp = dcanon(tcp, STRNULL); 159 cleanup_ignore(tcp); 160 cleanup_until(tcp); 161 #endif /* S_IFLNK */ 162 } 163 164 dp = xcalloc(sizeof(struct directory), 1); 165 dp->di_name = cp; 166 dp->di_count = 0; 167 dhead.di_next = dhead.di_prev = dp; 168 dp->di_next = dp->di_prev = &dhead; 169 printd = 0; 170 dnewcwd(dp, 0); 171 setcopy(STRdirstack, dp->di_name, VAR_READWRITE|VAR_NOGLOB); 172 } 173 174 static void 175 dset(Char *dp) 176 { 177 /* 178 * Don't call set() directly cause if the directory contains ` or 179 * other junk characters glob will fail. 180 */ 181 setcopy(STRowd, varval(STRcwd), VAR_READWRITE|VAR_NOGLOB); 182 setcopy(STRcwd, dp, VAR_READWRITE|VAR_NOGLOB); 183 tsetenv(STRPWD, dp); 184 } 185 186 #define DIR_PRINT 0x01 /* -p */ 187 #define DIR_LONG 0x02 /* -l */ 188 #define DIR_VERT 0x04 /* -v */ 189 #define DIR_LINE 0x08 /* -n */ 190 #define DIR_SAVE 0x10 /* -S */ 191 #define DIR_LOAD 0x20 /* -L */ 192 #define DIR_CLEAR 0x40 /* -c */ 193 #define DIR_OLD 0x80 /* - */ 194 195 static int 196 skipargs(Char ***v, const char *dstr, const char *str) 197 { 198 Char **n = *v, *s; 199 200 int dflag = 0, loop = 1; 201 for (n++; loop && *n != NULL && (*n)[0] == '-'; n++) 202 if (*(s = &((*n)[1])) == '\0') /* test for bare "-" argument */ 203 dflag |= DIR_OLD; 204 else if ((*n)[1] == '-' && (*n)[2] == '\0') { /* test for -- */ 205 n++; 206 break; 207 } else { 208 char *p; 209 while (*s != '\0') /* examine flags */ { 210 if ((p = strchr(dstr, *s++)) != NULL) 211 dflag |= (1 << (p - dstr)); 212 else 213 stderror(ERR_DIRUS, short2str(**v), dstr, str); 214 } 215 } 216 if (*n && (dflag & DIR_OLD)) 217 stderror(ERR_DIRUS, short2str(**v), dstr, str); 218 *v = n; 219 /* make -l, -v, and -n imply -p */ 220 if (dflag & (DIR_LONG|DIR_VERT|DIR_LINE)) 221 dflag |= DIR_PRINT; 222 return dflag; 223 } 224 225 /* 226 * dodirs - list all directories in directory loop 227 */ 228 /*ARGSUSED*/ 229 void 230 dodirs(Char **v, struct command *c) 231 { 232 static const char flags[] = "plvnSLc"; 233 int dflag = skipargs(&v, flags, ""); 234 235 USE(c); 236 if ((dflag & DIR_CLEAR) != 0) { 237 struct directory *dp, *fdp; 238 for (dp = dcwd->di_next; dp != dcwd; ) { 239 fdp = dp; 240 dp = dp->di_next; 241 if (fdp != &dhead) 242 dfree(fdp); 243 } 244 dhead.di_next = dhead.di_prev = dp; 245 dp->di_next = dp->di_prev = &dhead; 246 } 247 if ((dflag & DIR_LOAD) != 0) 248 loaddirs(*v); 249 else if ((dflag & DIR_SAVE) != 0) 250 recdirs(*v, 1); 251 252 if (*v && (dflag & (DIR_SAVE|DIR_LOAD))) 253 v++; 254 255 if (*v != NULL || (dflag & DIR_OLD)) 256 stderror(ERR_DIRUS, "dirs", flags, ""); 257 if ((dflag & (DIR_CLEAR|DIR_LOAD|DIR_SAVE)) == 0 || (dflag & DIR_PRINT)) 258 printdirs(dflag); 259 } 260 261 static void 262 printdirs(int dflag) 263 { 264 struct directory *dp; 265 Char *s, *user; 266 int idx, len, cur; 267 268 dp = dcwd; 269 idx = 0; 270 cur = 0; 271 do { 272 if (dp == &dhead) 273 continue; 274 if (dflag & DIR_VERT) { 275 xprintf("%d\t", idx++); 276 cur = 0; 277 } 278 s = dp->di_name; 279 user = NULL; 280 if (!(dflag & DIR_LONG) && (user = getusername(&s)) != NULL) 281 len = (int) (Strlen(user) + Strlen(s) + 2); 282 else 283 len = (int) (Strlen(s) + 1); 284 285 cur += len; 286 if ((dflag & DIR_LINE) && cur >= TermH - 1 && len < TermH) { 287 xputchar('\n'); 288 cur = len; 289 } 290 if (user) 291 xprintf("~%S", user); 292 xprintf("%-S%c", s, (dflag & DIR_VERT) ? '\n' : ' '); 293 } while ((dp = dp->di_prev) != dcwd); 294 if (!(dflag & DIR_VERT)) 295 xputchar('\n'); 296 } 297 298 void 299 dtildepr(Char *dir) 300 { 301 Char* user; 302 if ((user = getusername(&dir)) != NULL) 303 xprintf("~%-S%S", user, dir); 304 else 305 xprintf("%S", dir); 306 } 307 308 void 309 dtilde(void) 310 { 311 struct directory *d = dcwd; 312 313 do { 314 if (d == &dhead) 315 continue; 316 d->di_name = dcanon(d->di_name, STRNULL); 317 } while ((d = d->di_prev) != dcwd); 318 319 dset(dcwd->di_name); 320 } 321 322 323 /* dnormalize(): 324 * The path will be normalized if it 325 * 1) is "..", 326 * 2) or starts with "../", 327 * 3) or ends with "/..", 328 * 4) or contains the string "/../", 329 * then it will be normalized, unless those strings are quoted. 330 * Otherwise, a copy is made and sent back. 331 */ 332 Char * 333 dnormalize(const Char *cp, int expnd) 334 { 335 336 /* return true if dp is of the form "../xxx" or "/../xxx" */ 337 #define IS_DOTDOT(sp, p) (ISDOTDOT(p) && ((p) == (sp) || *((p) - 1) == '/')) 338 #define IS_DOT(sp, p) (ISDOT(p) && ((p) == (sp) || *((p) - 1) == '/')) 339 340 #ifdef S_IFLNK 341 if (expnd) { 342 struct Strbuf buf = Strbuf_INIT; 343 int dotdot = 0; 344 Char *dp, *cwd; 345 const Char *start = cp; 346 # ifdef HAVE_SLASHSLASH 347 int slashslash; 348 # endif /* HAVE_SLASHSLASH */ 349 350 /* 351 * count the number of "../xxx" or "xxx/../xxx" in the path 352 */ 353 for ( ; *cp && *(cp + 1); cp++) 354 if (IS_DOTDOT(start, cp)) 355 dotdot++; 356 357 /* 358 * if none, we are done. 359 */ 360 if (dotdot == 0) 361 return (Strsave(start)); 362 363 # ifdef notdef 364 struct stat sb; 365 /* 366 * We disable this test because: 367 * cd /tmp; mkdir dir1 dir2; cd dir2; ln -s /tmp/dir1; cd dir1; 368 * echo ../../dir1 does not expand. We had enabled this before 369 * because it was bothering people with expansions in compilation 370 * lines like -I../../foo. Maybe we need some kind of finer grain 371 * control? 372 * 373 * If the path doesn't exist, we are done too. 374 */ 375 if (lstat(short2str(start), &sb) != 0 && errno == ENOENT) 376 return (Strsave(start)); 377 # endif 378 379 cwd = xmalloc((Strlen(dcwd->di_name) + 3) * sizeof(Char)); 380 (void) Strcpy(cwd, dcwd->di_name); 381 382 /* 383 * If the path starts with a slash, we are not relative to 384 * the current working directory. 385 */ 386 if (ABSOLUTEP(start)) 387 *cwd = '\0'; 388 # ifdef HAVE_SLASHSLASH 389 slashslash = cwd[0] == '/' && cwd[1] == '/'; 390 # endif /* HAVE_SLASHSLASH */ 391 392 /* 393 * Ignore . and count ..'s 394 */ 395 cp = start; 396 do { 397 dotdot = 0; 398 buf.len = 0; 399 while (*cp) 400 if (IS_DOT(start, cp)) { 401 if (*++cp) 402 cp++; 403 } 404 else if (IS_DOTDOT(start, cp)) { 405 if (buf.len != 0) 406 break; /* finish analyzing .././../xxx/[..] */ 407 dotdot++; 408 cp += 2; 409 if (*cp) 410 cp++; 411 } 412 else 413 Strbuf_append1(&buf, *cp++); 414 415 Strbuf_terminate(&buf); 416 while (dotdot > 0) 417 if ((dp = Strrchr(cwd, '/')) != NULL) { 418 # ifdef HAVE_SLASHSLASH 419 if (dp == &cwd[1]) 420 slashslash = 1; 421 # endif /* HAVE_SLASHSLASH */ 422 *dp = '\0'; 423 dotdot--; 424 } 425 else 426 break; 427 428 if (!*cwd) { /* too many ..'s, starts with "/" */ 429 cwd[0] = '/'; 430 # ifdef HAVE_SLASHSLASH 431 /* 432 * Only append another slash, if already the former cwd 433 * was in a double-slash path. 434 */ 435 cwd[1] = slashslash ? '/' : '\0'; 436 cwd[2] = '\0'; 437 # else /* !HAVE_SLASHSLASH */ 438 cwd[1] = '\0'; 439 # endif /* HAVE_SLASHSLASH */ 440 } 441 # ifdef HAVE_SLASHSLASH 442 else if (slashslash && cwd[1] == '\0') { 443 cwd[1] = '/'; 444 cwd[2] = '\0'; 445 } 446 # endif /* HAVE_SLASHSLASH */ 447 448 if (buf.len != 0) { 449 size_t i; 450 451 i = Strlen(cwd); 452 if (TRM(cwd[i - 1]) != '/') { 453 cwd[i++] = '/'; 454 cwd[i] = '\0'; 455 } 456 dp = Strspl(cwd, TRM(buf.s[0]) == '/' ? &buf.s[1] : buf.s); 457 xfree(cwd); 458 cwd = dp; 459 i = Strlen(cwd) - 1; 460 if (TRM(cwd[i]) == '/') 461 cwd[i] = '\0'; 462 } 463 /* Reduction of ".." following the stuff we collected in buf 464 * only makes sense if the directory item in buf really exists. 465 * Avoid reduction of "-I../.." (typical compiler call) to "" 466 * or "/usr/nonexistant/../bin" to "/usr/bin": 467 */ 468 if (cwd[0]) { 469 struct stat exists; 470 if (0 != stat(short2str(cwd), &exists)) { 471 xfree(buf.s); 472 xfree(cwd); 473 return Strsave(start); 474 } 475 } 476 } while (*cp != '\0'); 477 xfree(buf.s); 478 return cwd; 479 } 480 #endif /* S_IFLNK */ 481 return Strsave(cp); 482 } 483 484 485 /* 486 * dochngd - implement chdir command. 487 */ 488 /*ARGSUSED*/ 489 void 490 dochngd(Char **v, struct command *c) 491 { 492 Char *cp; 493 struct directory *dp; 494 int dflag = skipargs(&v, "plvn", "[-|<dir>]"); 495 496 USE(c); 497 printd = 0; 498 cp = (dflag & DIR_OLD) ? varval(STRowd) : *v; 499 500 if (cp == NULL) { 501 if (!cdtohome) 502 stderror(ERR_NAME | ERR_TOOFEW); 503 else if ((cp = varval(STRhome)) == STRNULL || *cp == 0) 504 stderror(ERR_NAME | ERR_NOHOMEDIR); 505 if (chdir(short2str(cp)) < 0) 506 stderror(ERR_NAME | ERR_CANTCHANGE); 507 cp = Strsave(cp); 508 } 509 else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) { 510 stderror(ERR_NAME | ERR_TOOMANY); 511 /* NOTREACHED */ 512 return; 513 } 514 else if ((dp = dfind(cp)) != 0) { 515 char *tmp; 516 517 printd = 1; 518 if (chdir(tmp = short2str(dp->di_name)) < 0) 519 stderror(ERR_SYSTEM, tmp, strerror(errno)); 520 dcwd->di_prev->di_next = dcwd->di_next; 521 dcwd->di_next->di_prev = dcwd->di_prev; 522 dfree(dcwd); 523 dnewcwd(dp, dflag); 524 return; 525 } 526 else 527 if ((cp = dfollow(cp, dflag & DIR_OLD)) == NULL) 528 return; 529 dp = xcalloc(sizeof(struct directory), 1); 530 dp->di_name = cp; 531 dp->di_count = 0; 532 dp->di_next = dcwd->di_next; 533 dp->di_prev = dcwd->di_prev; 534 dp->di_prev->di_next = dp; 535 dp->di_next->di_prev = dp; 536 dfree(dcwd); 537 dnewcwd(dp, dflag); 538 } 539 540 static Char * 541 dgoto(Char *cp) 542 { 543 Char *dp, *ret; 544 545 if (!ABSOLUTEP(cp)) 546 { 547 Char *p, *q; 548 size_t cwdlen; 549 550 cwdlen = Strlen(dcwd->di_name); 551 if (cwdlen == 1) /* root */ 552 cwdlen = 0; 553 dp = xmalloc((cwdlen + Strlen(cp) + 2) * sizeof(Char)); 554 for (p = dp, q = dcwd->di_name; (*p++ = *q++) != '\0';) 555 continue; 556 if (cwdlen) 557 p[-1] = '/'; 558 else 559 p--; /* don't add a / after root */ 560 Strcpy(p, cp); 561 xfree(cp); 562 cp = dp; 563 dp += cwdlen; 564 } 565 else 566 dp = cp; 567 568 #if defined(WINNT_NATIVE) 569 return agetcwd(); 570 #elif defined(__CYGWIN__) 571 if (ABSOLUTEP(cp) && cp[1] == ':') { /* Only DOS paths are treated that way */ 572 return agetcwd(); 573 } else { 574 cleanup_push(cp, xfree); 575 ret = dcanon(cp, dp); 576 cleanup_ignore(cp); 577 cleanup_until(cp); 578 } 579 #else /* !WINNT_NATIVE */ 580 cleanup_push(cp, xfree); 581 ret = dcanon(cp, dp); 582 cleanup_ignore(cp); 583 cleanup_until(cp); 584 #endif /* WINNT_NATIVE */ 585 return ret; 586 } 587 588 /* 589 * dfollow - change to arg directory; fall back on cdpath if not valid 590 */ 591 static Char * 592 dfollow(Char *cp, int old) 593 { 594 Char *dp; 595 struct varent *c; 596 int serrno; 597 598 cp = old ? Strsave(cp) : globone(cp, G_ERROR); 599 cleanup_push(cp, xfree); 600 #ifdef apollo 601 if (Strchr(cp, '`')) { 602 char *dptr; 603 if (chdir(dptr = short2str(cp)) < 0) 604 stderror(ERR_SYSTEM, dptr, strerror(errno)); 605 dp = agetcwd(); 606 cleanup_push(dp, xfree); 607 if (dp != NULL) { 608 cleanup_until(cp); 609 return dgoto(dp); 610 } 611 else 612 stderror(ERR_SYSTEM, dptr, strerror(errno)); 613 } 614 #endif /* apollo */ 615 616 /* 617 * if we are ignoring symlinks, try to fix relatives now. 618 * if we are expading symlinks, it should be done by now. 619 */ 620 dp = dnormalize(cp, symlinks == SYM_IGNORE); 621 if (chdir(short2str(dp)) >= 0) { 622 cleanup_until(cp); 623 return dgoto(dp); 624 } 625 else { 626 xfree(dp); 627 if (chdir(short2str(cp)) >= 0) { 628 cleanup_ignore(cp); 629 cleanup_until(cp); 630 return dgoto(cp); 631 } 632 else if (errno != ENOENT && errno != ENOTDIR) { 633 int err; 634 635 err = errno; 636 stderror(ERR_SYSTEM, short2str(cp), strerror(err)); 637 } 638 serrno = errno; 639 } 640 641 if (cp[0] != '/' && !prefix(STRdotsl, cp) && !prefix(STRdotdotsl, cp) 642 && (c = adrof(STRcdpath)) && c->vec != NULL) { 643 struct Strbuf buf = Strbuf_INIT; 644 Char **cdp; 645 646 for (cdp = c->vec; *cdp; cdp++) { 647 size_t len = Strlen(*cdp); 648 buf.len = 0; 649 if (len > 0) { 650 Strbuf_append(&buf, *cdp); 651 if ((*cdp)[len - 1] != '/') 652 Strbuf_append1(&buf, '/'); 653 } 654 Strbuf_append(&buf, cp); 655 Strbuf_terminate(&buf); 656 /* 657 * We always want to fix the directory here 658 * If we are normalizing symlinks 659 */ 660 dp = dnormalize(buf.s, symlinks == SYM_IGNORE || 661 symlinks == SYM_EXPAND); 662 if (chdir(short2str(dp)) >= 0) { 663 printd = 1; 664 xfree(buf.s); 665 cleanup_until(cp); 666 return dgoto(dp); 667 } 668 else if (chdir(short2str(cp)) >= 0) { 669 printd = 1; 670 xfree(dp); 671 xfree(buf.s); 672 cleanup_ignore(cp); 673 cleanup_until(cp); 674 return dgoto(cp); 675 } 676 xfree(dp); 677 } 678 xfree(buf.s); 679 } 680 dp = varval(cp); 681 if ((dp[0] == '/' || dp[0] == '.') && chdir(short2str(dp)) >= 0) { 682 cleanup_until(cp); 683 cp = Strsave(dp); 684 printd = 1; 685 return dgoto(cp); 686 } 687 /* 688 * on login source of ~/.cshdirs, errors are eaten. the dir stack is all 689 * directories we could get to. 690 */ 691 if (!bequiet) 692 stderror(ERR_SYSTEM, short2str(cp), strerror(serrno)); 693 cleanup_until(cp); 694 return (NULL); 695 } 696 697 698 /* 699 * dopushd - push new directory onto directory stack. 700 * with no arguments exchange top and second. 701 * with numeric argument (+n) bring it to top. 702 */ 703 /*ARGSUSED*/ 704 void 705 dopushd(Char **v, struct command *c) 706 { 707 struct directory *dp; 708 Char *cp; 709 int dflag = skipargs(&v, "plvn", " [-|<dir>|+<n>]"); 710 711 USE(c); 712 printd = 1; 713 cp = (dflag & DIR_OLD) ? varval(STRowd) : *v; 714 715 if (cp == NULL) { 716 if (adrof(STRpushdtohome)) { 717 if ((cp = varval(STRhome)) == STRNULL || *cp == 0) 718 stderror(ERR_NAME | ERR_NOHOMEDIR); 719 if (chdir(short2str(cp)) < 0) 720 stderror(ERR_NAME | ERR_CANTCHANGE); 721 if ((cp = dfollow(cp, dflag & DIR_OLD)) == NULL) 722 return; 723 dp = xcalloc(sizeof(struct directory), 1); 724 dp->di_name = cp; 725 dp->di_count = 0; 726 dp->di_prev = dcwd; 727 dp->di_next = dcwd->di_next; 728 dcwd->di_next = dp; 729 dp->di_next->di_prev = dp; 730 } 731 else { 732 char *tmp; 733 734 if ((dp = dcwd->di_prev) == &dhead) 735 dp = dhead.di_prev; 736 if (dp == dcwd) 737 stderror(ERR_NAME | ERR_NODIR); 738 if (chdir(tmp = short2str(dp->di_name)) < 0) 739 stderror(ERR_SYSTEM, tmp, strerror(errno)); 740 dp->di_prev->di_next = dp->di_next; 741 dp->di_next->di_prev = dp->di_prev; 742 dp->di_next = dcwd->di_next; 743 dp->di_prev = dcwd; 744 dcwd->di_next->di_prev = dp; 745 dcwd->di_next = dp; 746 } 747 } 748 else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) { 749 stderror(ERR_NAME | ERR_TOOMANY); 750 /* NOTREACHED */ 751 return; 752 } 753 else if ((dp = dfind(cp)) != NULL) { 754 char *tmp; 755 756 if (chdir(tmp = short2str(dp->di_name)) < 0) 757 stderror(ERR_SYSTEM, tmp, strerror(errno)); 758 /* 759 * kfk - 10 Feb 1984 - added new "extraction style" pushd +n 760 */ 761 if (adrof(STRdextract)) 762 dextract(dp); 763 } 764 else { 765 Char *ccp; 766 767 if ((ccp = dfollow(cp, dflag & DIR_OLD)) == NULL) 768 return; 769 dp = xcalloc(sizeof(struct directory), 1); 770 dp->di_name = ccp; 771 dp->di_count = 0; 772 dp->di_prev = dcwd; 773 dp->di_next = dcwd->di_next; 774 dcwd->di_next = dp; 775 dp->di_next->di_prev = dp; 776 } 777 dnewcwd(dp, dflag); 778 } 779 780 /* 781 * dfind - find a directory if specified by numeric (+n) argument 782 */ 783 static struct directory * 784 dfind(Char *cp) 785 { 786 struct directory *dp; 787 int i; 788 Char *ep; 789 790 if (*cp++ != '+') 791 return (0); 792 for (ep = cp; Isdigit(*ep); ep++) 793 continue; 794 if (*ep) 795 return (0); 796 i = getn(cp); 797 if (i <= 0) 798 return (0); 799 for (dp = dcwd; i != 0; i--) { 800 if ((dp = dp->di_prev) == &dhead) 801 dp = dp->di_prev; 802 if (dp == dcwd) 803 stderror(ERR_NAME | ERR_DEEP); 804 } 805 return (dp); 806 } 807 808 /* 809 * dopopd - pop a directory out of the directory stack 810 * with a numeric argument just discard it. 811 */ 812 /*ARGSUSED*/ 813 void 814 dopopd(Char **v, struct command *c) 815 { 816 Char *cp; 817 struct directory *dp, *p = NULL; 818 int dflag = skipargs(&v, "plvn", " [-|+<n>]"); 819 820 USE(c); 821 printd = 1; 822 cp = (dflag & DIR_OLD) ? varval(STRowd) : *v; 823 824 if (cp == NULL) 825 dp = dcwd; 826 else if ((dflag & DIR_OLD) == 0 && v[1] != NULL) { 827 stderror(ERR_NAME | ERR_TOOMANY); 828 /* NOTREACHED */ 829 return; 830 } 831 else if ((dp = dfind(cp)) == 0) 832 stderror(ERR_NAME | ERR_BADDIR); 833 if (dp->di_prev == &dhead && dp->di_next == &dhead) 834 stderror(ERR_NAME | ERR_EMPTY); 835 if (dp == dcwd) { 836 char *tmp; 837 838 if ((p = dp->di_prev) == &dhead) 839 p = dhead.di_prev; 840 if (chdir(tmp = short2str(p->di_name)) < 0) 841 stderror(ERR_SYSTEM, tmp, strerror(errno)); 842 } 843 dp->di_prev->di_next = dp->di_next; 844 dp->di_next->di_prev = dp->di_prev; 845 dfree(dp); 846 if (dp == dcwd) { 847 dnewcwd(p, dflag); 848 } 849 else { 850 printdirs(dflag); 851 } 852 } 853 854 /* 855 * dfree - free the directory (or keep it if it still has ref count) 856 */ 857 void 858 dfree(struct directory *dp) 859 { 860 861 if (dp->di_count != 0) { 862 dp->di_next = dp->di_prev = 0; 863 } 864 else { 865 xfree(dp->di_name); 866 xfree(dp); 867 } 868 } 869 870 /* 871 * dcanon - canonicalize the pathname, removing excess ./ and ../ etc. 872 * we are of course assuming that the file system is standardly 873 * constructed (always have ..'s, directories have links) 874 */ 875 Char * 876 dcanon(Char *cp, Char *p) 877 { 878 Char *sp; 879 Char *p1, *p2; /* general purpose */ 880 int slash; 881 #ifdef HAVE_SLASHSLASH 882 int slashslash; 883 #endif /* HAVE_SLASHSLASH */ 884 size_t clen; 885 886 #ifdef S_IFLNK /* if we have symlinks */ 887 Char *mlink, *newcp; 888 char *tlink; 889 size_t cc; 890 #endif /* S_IFLNK */ 891 892 clen = Strlen(cp); 893 894 /* 895 * christos: if the path given does not start with a slash prepend cwd. If 896 * cwd does not start with a slash or the result would be too long try to 897 * correct it. 898 */ 899 if (!ABSOLUTEP(cp)) { 900 Char *tmpdir; 901 size_t len; 902 903 p1 = varval(STRcwd); 904 if (p1 == STRNULL || !ABSOLUTEP(p1)) { 905 Char *new_cwd = agetcwd(); 906 907 if (new_cwd == NULL) { 908 xprintf("%s: %s\n", progname, strerror(errno)); 909 setcopy(STRcwd, str2short("/"), VAR_READWRITE|VAR_NOGLOB); 910 } 911 else 912 setv(STRcwd, new_cwd, VAR_READWRITE|VAR_NOGLOB); 913 p1 = varval(STRcwd); 914 } 915 len = Strlen(p1); 916 tmpdir = xmalloc((len + clen + 2) * sizeof (*tmpdir)); 917 (void) Strcpy(tmpdir, p1); 918 (void) Strcat(tmpdir, STRslash); 919 (void) Strcat(tmpdir, cp); 920 xfree(cp); 921 cp = p = tmpdir; 922 } 923 924 #ifdef HAVE_SLASHSLASH 925 slashslash = (cp[0] == '/' && cp[1] == '/'); 926 #endif /* HAVE_SLASHSLASH */ 927 928 while (*p) { /* for each component */ 929 sp = p; /* save slash address */ 930 while (*++p == '/') /* flush extra slashes */ 931 continue; 932 if (p != ++sp) 933 for (p1 = sp, p2 = p; (*p1++ = *p2++) != '\0';) 934 continue; 935 p = sp; /* save start of component */ 936 slash = 0; 937 if (*p) 938 while (*++p) /* find next slash or end of path */ 939 if (*p == '/') { 940 slash = 1; 941 *p = 0; 942 break; 943 } 944 945 #ifdef HAVE_SLASHSLASH 946 if (&cp[1] == sp && sp[0] == '.' && sp[1] == '.' && sp[2] == '\0') 947 slashslash = 1; 948 #endif /* HAVE_SLASHSLASH */ 949 if (*sp == '\0') { /* if component is null */ 950 if (--sp == cp) /* if path is one char (i.e. /) */ 951 break; 952 else 953 *sp = '\0'; 954 } 955 else if (sp[0] == '.' && sp[1] == 0) { 956 if (slash) { 957 for (p1 = sp, p2 = p + 1; (*p1++ = *p2++) != '\0';) 958 continue; 959 p = --sp; 960 } 961 else if (--sp != cp) 962 *sp = '\0'; 963 else 964 sp[1] = '\0'; 965 } 966 else if (sp[0] == '.' && sp[1] == '.' && sp[2] == 0) { 967 /* 968 * We have something like "yyy/xxx/..", where "yyy" can be null or 969 * a path starting at /, and "xxx" is a single component. Before 970 * compressing "xxx/..", we want to expand "yyy/xxx", if it is a 971 * symbolic link. 972 */ 973 *--sp = 0; /* form the pathname for readlink */ 974 #ifdef S_IFLNK /* if we have symlinks */ 975 if (sp != cp && /* symlinks != SYM_IGNORE && */ 976 (tlink = areadlink(short2str(cp))) != NULL) { 977 mlink = str2short(tlink); 978 xfree(tlink); 979 980 if (slash) 981 *p = '/'; 982 /* 983 * Point p to the '/' in "/..", and restore the '/'. 984 */ 985 *(p = sp) = '/'; 986 if (*mlink != '/') { 987 /* 988 * Relative path, expand it between the "yyy/" and the 989 * "/..". First, back sp up to the character past "yyy/". 990 */ 991 while (*--sp != '/') 992 continue; 993 sp++; 994 *sp = 0; 995 /* 996 * New length is "yyy/" + mlink + "/.." and rest 997 */ 998 p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) + 999 Strlen(p) + 1) * sizeof(Char)); 1000 /* 1001 * Copy new path into newcp 1002 */ 1003 for (p2 = cp; (*p1++ = *p2++) != '\0';) 1004 continue; 1005 for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';) 1006 continue; 1007 for (p1--, p2 = p; (*p1++ = *p2++) != '\0';) 1008 continue; 1009 /* 1010 * Restart canonicalization at expanded "/xxx". 1011 */ 1012 p = sp - cp - 1 + newcp; 1013 } 1014 else { 1015 newcp = Strspl(mlink, p); 1016 /* 1017 * Restart canonicalization at beginning 1018 */ 1019 p = newcp; 1020 } 1021 xfree(cp); 1022 cp = newcp; 1023 #ifdef HAVE_SLASHSLASH 1024 slashslash = (cp[0] == '/' && cp[1] == '/'); 1025 #endif /* HAVE_SLASHSLASH */ 1026 continue; /* canonicalize the link */ 1027 } 1028 #endif /* S_IFLNK */ 1029 *sp = '/'; 1030 if (sp != cp) 1031 while (*--sp != '/') 1032 continue; 1033 if (slash) { 1034 for (p1 = sp + 1, p2 = p + 1; (*p1++ = *p2++) != '\0';) 1035 continue; 1036 p = sp; 1037 } 1038 else if (cp == sp) 1039 *++sp = '\0'; 1040 else 1041 *sp = '\0'; 1042 } 1043 else { /* normal dir name (not . or .. or nothing) */ 1044 1045 #ifdef S_IFLNK /* if we have symlinks */ 1046 if (sp != cp && symlinks == SYM_CHASE && 1047 (tlink = areadlink(short2str(cp))) != NULL) { 1048 mlink = str2short(tlink); 1049 xfree(tlink); 1050 1051 /* 1052 * restore the '/'. 1053 */ 1054 if (slash) 1055 *p = '/'; 1056 1057 /* 1058 * point sp to p (rather than backing up). 1059 */ 1060 sp = p; 1061 1062 if (*mlink != '/') { 1063 /* 1064 * Relative path, expand it between the "yyy/" and the 1065 * remainder. First, back sp up to the character past 1066 * "yyy/". 1067 */ 1068 while (*--sp != '/') 1069 continue; 1070 sp++; 1071 *sp = 0; 1072 /* 1073 * New length is "yyy/" + mlink + "/.." and rest 1074 */ 1075 p1 = newcp = xmalloc(((sp - cp) + Strlen(mlink) + 1076 Strlen(p) + 1) * sizeof(Char)); 1077 /* 1078 * Copy new path into newcp 1079 */ 1080 for (p2 = cp; (*p1++ = *p2++) != '\0';) 1081 continue; 1082 for (p1--, p2 = mlink; (*p1++ = *p2++) != '\0';) 1083 continue; 1084 for (p1--, p2 = p; (*p1++ = *p2++) != '\0';) 1085 continue; 1086 /* 1087 * Restart canonicalization at expanded "/xxx". 1088 */ 1089 p = sp - cp - 1 + newcp; 1090 } 1091 else { 1092 newcp = Strspl(mlink, p); 1093 /* 1094 * Restart canonicalization at beginning 1095 */ 1096 p = newcp; 1097 } 1098 xfree(cp); 1099 cp = newcp; 1100 #ifdef HAVE_SLASHSLASH 1101 slashslash = (cp[0] == '/' && cp[1] == '/'); 1102 #endif /* HAVE_SLASHSLASH */ 1103 continue; /* canonicalize the mlink */ 1104 } 1105 #endif /* S_IFLNK */ 1106 if (slash) 1107 *p = '/'; 1108 } 1109 } 1110 1111 /* 1112 * fix home... 1113 */ 1114 #ifdef S_IFLNK 1115 p1 = varval(STRhome); 1116 cc = Strlen(p1); 1117 /* 1118 * See if we're not in a subdir of STRhome 1119 */ 1120 if (p1 && *p1 == '/' && (Strncmp(p1, cp, cc) != 0 || 1121 (cp[cc] != '/' && cp[cc] != '\0'))) { 1122 static ino_t home_ino = (ino_t) -1; 1123 static dev_t home_dev = (dev_t) -1; 1124 static Char *home_ptr = NULL; 1125 struct stat statbuf; 1126 int found; 1127 Char *copy; 1128 1129 /* 1130 * Get dev and ino of STRhome 1131 */ 1132 if (home_ptr != p1 && 1133 stat(short2str(p1), &statbuf) != -1) { 1134 home_dev = statbuf.st_dev; 1135 home_ino = statbuf.st_ino; 1136 home_ptr = p1; 1137 } 1138 /* 1139 * Start comparing dev & ino backwards 1140 */ 1141 p2 = copy = Strsave(cp); 1142 found = 0; 1143 while (*p2 && stat(short2str(p2), &statbuf) != -1) { 1144 if (DEV_DEV_COMPARE(statbuf.st_dev, home_dev) && 1145 statbuf.st_ino == home_ino) { 1146 found = 1; 1147 break; 1148 } 1149 if ((sp = Strrchr(p2, '/')) != NULL) 1150 *sp = '\0'; 1151 } 1152 /* 1153 * See if we found it 1154 */ 1155 if (*p2 && found) { 1156 /* 1157 * Use STRhome to make '~' work 1158 */ 1159 newcp = Strspl(p1, cp + Strlen(p2)); 1160 xfree(cp); 1161 cp = newcp; 1162 } 1163 xfree(copy); 1164 } 1165 #endif /* S_IFLNK */ 1166 1167 #ifdef HAVE_SLASHSLASH 1168 if (slashslash) { 1169 if (cp[1] != '/') { 1170 p = xmalloc((Strlen(cp) + 2) * sizeof(Char)); 1171 *p = '/'; 1172 (void) Strcpy(&p[1], cp); 1173 xfree(cp); 1174 cp = p; 1175 } 1176 } 1177 if (cp[1] == '/' && cp[2] == '/') { 1178 for (p1 = &cp[1], p2 = &cp[2]; (*p1++ = *p2++) != '\0';) 1179 continue; 1180 } 1181 #endif /* HAVE_SLASHSLASH */ 1182 return cp; 1183 } 1184 1185 1186 /* 1187 * dnewcwd - make a new directory in the loop the current one 1188 */ 1189 static void 1190 dnewcwd(struct directory *dp, int dflag) 1191 { 1192 int print; 1193 1194 if (adrof(STRdunique)) { 1195 struct directory *dn; 1196 1197 for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev) 1198 if (dn != dp && Strcmp(dn->di_name, dp->di_name) == 0) { 1199 dn->di_next->di_prev = dn->di_prev; 1200 dn->di_prev->di_next = dn->di_next; 1201 dfree(dn); 1202 break; 1203 } 1204 } 1205 dcwd = dp; 1206 dset(dcwd->di_name); 1207 dgetstack(); 1208 print = printd; /* if printd is set, print dirstack... */ 1209 if (adrof(STRpushdsilent)) /* but pushdsilent overrides printd... */ 1210 print = 0; 1211 if (dflag & DIR_PRINT) /* but DIR_PRINT overrides pushdsilent... */ 1212 print = 1; 1213 if (bequiet) /* and bequiet overrides everything */ 1214 print = 0; 1215 if (print) 1216 printdirs(dflag); 1217 cwd_cmd(); /* PWP: run the defined cwd command */ 1218 } 1219 1220 void 1221 dsetstack(void) 1222 { 1223 Char **cp; 1224 struct varent *vp; 1225 struct directory *dn, *dp; 1226 1227 if ((vp = adrof(STRdirstack)) == NULL || vp->vec == NULL) 1228 return; 1229 1230 /* Free the whole stack */ 1231 while ((dn = dhead.di_prev) != &dhead) { 1232 dn->di_next->di_prev = dn->di_prev; 1233 dn->di_prev->di_next = dn->di_next; 1234 if (dn != dcwd) 1235 dfree(dn); 1236 } 1237 1238 /* thread the current working directory */ 1239 dhead.di_prev = dhead.di_next = dcwd; 1240 dcwd->di_next = dcwd->di_prev = &dhead; 1241 1242 /* put back the stack */ 1243 for (cp = vp->vec; cp && *cp && **cp; cp++) { 1244 dp = xcalloc(sizeof(struct directory), 1); 1245 dp->di_name = Strsave(*cp); 1246 dp->di_count = 0; 1247 dp->di_prev = dcwd; 1248 dp->di_next = dcwd->di_next; 1249 dcwd->di_next = dp; 1250 dp->di_next->di_prev = dp; 1251 } 1252 dgetstack(); /* Make $dirstack reflect the current state */ 1253 } 1254 1255 static void 1256 dgetstack(void) 1257 { 1258 int i = 0; 1259 Char **dblk, **dbp; 1260 struct directory *dn; 1261 1262 if (adrof(STRdirstack) == NULL) 1263 return; 1264 1265 for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, i++) 1266 continue; 1267 dbp = dblk = xmalloc((i + 1) * sizeof(Char *)); 1268 for (dn = dhead.di_prev; dn != &dhead; dn = dn->di_prev, dbp++) 1269 *dbp = Strsave(dn->di_name); 1270 *dbp = NULL; 1271 cleanup_push(dblk, blk_cleanup); 1272 setq(STRdirstack, dblk, &shvhed, VAR_READWRITE); 1273 cleanup_ignore(dblk); 1274 cleanup_until(dblk); 1275 } 1276 1277 /* 1278 * getstakd - added by kfk 17 Jan 1984 1279 * Support routine for the stack hack. Finds nth directory in 1280 * the directory stack, or finds last directory in stack. 1281 */ 1282 const Char * 1283 getstakd(int cnt) 1284 { 1285 struct directory *dp; 1286 1287 dp = dcwd; 1288 if (cnt < 0) { /* < 0 ==> last dir requested. */ 1289 dp = dp->di_next; 1290 if (dp == &dhead) 1291 dp = dp->di_next; 1292 } 1293 else { 1294 while (cnt-- > 0) { 1295 dp = dp->di_prev; 1296 if (dp == &dhead) 1297 dp = dp->di_prev; 1298 if (dp == dcwd) 1299 return NULL; 1300 } 1301 } 1302 return dp->di_name; 1303 } 1304 1305 /* 1306 * Karl Kleinpaste - 10 Feb 1984 1307 * Added dextract(), which is used in pushd +n. 1308 * Instead of just rotating the entire stack around, dextract() 1309 * lets the user have the nth dir extracted from its current 1310 * position, and pushes it onto the top. 1311 */ 1312 static void 1313 dextract(struct directory *dp) 1314 { 1315 if (dp == dcwd) 1316 return; 1317 dp->di_next->di_prev = dp->di_prev; 1318 dp->di_prev->di_next = dp->di_next; 1319 dp->di_next = dcwd->di_next; 1320 dp->di_prev = dcwd; 1321 dp->di_next->di_prev = dp; 1322 dcwd->di_next = dp; 1323 } 1324 1325 static void 1326 bequiet_cleanup(void *dummy) 1327 { 1328 USE(dummy); 1329 bequiet = 0; 1330 } 1331 1332 void 1333 loaddirs(Char *fname) 1334 { 1335 static Char *loaddirs_cmd[] = { STRsource, NULL, NULL }; 1336 1337 bequiet = 1; 1338 cleanup_push(&bequiet, bequiet_cleanup); 1339 if (fname) 1340 loaddirs_cmd[1] = fname; 1341 else if ((fname = varval(STRdirsfile)) != STRNULL) 1342 loaddirs_cmd[1] = fname; 1343 else 1344 loaddirs_cmd[1] = STRtildotdirs; 1345 dosource(loaddirs_cmd, NULL); 1346 cleanup_until(&bequiet); 1347 } 1348 1349 /* 1350 * create a file called ~/.cshdirs which has a sequence 1351 * of pushd commands which will restore the dir stack to 1352 * its state before exit/logout. remember that the order 1353 * is reversed in the file because we are pushing. 1354 * -strike 1355 */ 1356 void 1357 recdirs(Char *fname, int def) 1358 { 1359 int fp, ftmp, oldidfds; 1360 int cdflag = 0; 1361 struct directory *dp; 1362 unsigned int num; 1363 Char *snum; 1364 struct Strbuf qname = Strbuf_INIT; 1365 1366 if (fname == NULL && !def) 1367 return; 1368 1369 if (fname == NULL) { 1370 if ((fname = varval(STRdirsfile)) == STRNULL) 1371 fname = Strspl(varval(STRhome), &STRtildotdirs[1]); 1372 else 1373 fname = Strsave(fname); 1374 } 1375 else 1376 fname = globone(fname, G_ERROR); 1377 cleanup_push(fname, xfree); 1378 1379 if ((fp = xcreat(short2str(fname), 0600)) == -1) { 1380 cleanup_until(fname); 1381 return; 1382 } 1383 1384 if ((snum = varval(STRsavedirs)) == STRNULL || snum[0] == '\0') 1385 num = (unsigned int) ~0; 1386 else 1387 num = (unsigned int) atoi(short2str(snum)); 1388 1389 oldidfds = didfds; 1390 didfds = 0; 1391 ftmp = SHOUT; 1392 SHOUT = fp; 1393 1394 cleanup_push(&qname, Strbuf_cleanup); 1395 dp = dcwd->di_next; 1396 do { 1397 if (dp == &dhead) 1398 continue; 1399 1400 if (cdflag == 0) { 1401 cdflag = 1; 1402 xprintf("cd %S\n", quote_meta(&qname, dp->di_name)); 1403 } 1404 else 1405 xprintf("pushd %S\n", quote_meta(&qname, dp->di_name)); 1406 1407 if (num-- == 0) 1408 break; 1409 1410 } while ((dp = dp->di_next) != dcwd->di_next); 1411 1412 xclose(fp); 1413 SHOUT = ftmp; 1414 didfds = oldidfds; 1415 cleanup_until(fname); 1416 } 1417