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