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