1 /* $NetBSD: cd.c,v 1.56 2025/08/18 18:58:59 kre Exp $ */ 2 3 /*- 4 * Copyright (c) 1991, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Kenneth Almquist. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <sys/cdefs.h> 36 #ifndef lint 37 #if 0 38 static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; 39 #else 40 __RCSID("$NetBSD: cd.c,v 1.56 2025/08/18 18:58:59 kre Exp $"); 41 #endif 42 #endif /* not lint */ 43 44 #include <sys/types.h> 45 #include <sys/stat.h> 46 #include <stdbool.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <unistd.h> 50 #include <errno.h> 51 52 /* 53 * The cd and pwd commands. 54 */ 55 56 #include "shell.h" 57 #include "var.h" 58 #include "nodes.h" /* for jobs.h */ 59 #include "jobs.h" 60 #include "options.h" 61 #include "builtins.h" 62 #include "output.h" 63 #include "memalloc.h" 64 #include "error.h" 65 #include "exec.h" 66 #include "redir.h" 67 #include "mystring.h" 68 #include "show.h" 69 #include "cd.h" 70 71 STATIC int docd(const char *, bool, bool); 72 STATIC char *getcomponent(void); 73 STATIC bool updatepwd(const char *); 74 STATIC void find_curdir(int noerror); 75 STATIC bool is_curdir(const char *); 76 77 char *curdir = NULL; /* current working directory */ 78 char *prevdir; /* previous working directory */ 79 STATIC char *cdcomppath; 80 81 int 82 cdcmd(int argc, char **argv) 83 { 84 const char *dest; 85 const char *path, *cp; 86 char *p; 87 char *d; 88 struct stat statb; 89 int print = cdprint; /* set -o cdprint to enable */ 90 int err = ENOENT; 91 int err_set = -1; 92 bool eopt = false; 93 char opt; 94 95 while ((opt = nextopt("Pe")) != '\0') 96 if (opt == 'e') 97 eopt = true; 98 99 /* 100 * Try (quite hard) to have 'curdir' defined, nothing has set 101 * it on entry to the shell, but we want 'cd fred; cd -' to work. 102 */ 103 getpwd(1); 104 dest = *argptr; 105 if (dest == NULL) { 106 dest = bltinlookup("HOME", 1); 107 if (dest == NULL) 108 error("HOME not set"); 109 } else if (argptr[1]) { 110 /* Do 'ksh' style substitution */ 111 if (!curdir) 112 error("PWD not set"); 113 p = strstr(curdir, dest); 114 if (!p) 115 error("bad substitution (no \"%s\" in \"%s\")", 116 dest, curdir); 117 d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1); 118 memcpy(d, curdir, p - curdir); 119 strcpy(d + (p - curdir), argptr[1]); 120 strcat(d, p + strlen(dest)); 121 dest = d; 122 print = 1; 123 } else if (dest[0] == '-' && dest[1] == '\0') { 124 dest = prevdir ? prevdir : curdir; 125 print = 1; 126 } 127 #if 0 /* POSIX 2024 says this must be an error */ 128 if (*dest == '\0') 129 dest = "."; 130 #endif 131 132 cp = dest; 133 if (*cp == '.' && *++cp == '.') 134 cp++; 135 if (*cp == 0 || *cp == '/' || (path = bltinlookup("CDPATH", 1)) == NULL) 136 path = nullstr; 137 138 cp = dest; 139 while ((p = padvance(&path, dest, 0)) != NULL) { 140 int dopr = print; 141 int x; 142 143 stunalloc(p); 144 /* until the next stack write, p remains valid */ 145 146 if (stat(p, &statb) < 0) { 147 if (err_set < 0 && errno != ENOENT) { 148 err = errno; 149 cp = stalloc((int)strlen(p) + 1); 150 err_set = 0; 151 } 152 continue; 153 } 154 if (!S_ISDIR(statb.st_mode)) { 155 if (err_set <= 0) { 156 err = ENOTDIR; 157 cp = stalloc((int)strlen(p) + 1); 158 err_set = 1; 159 } 160 continue; 161 } 162 163 if (!print) 164 dopr = strcmp(p, dest); 165 166 if ((x = docd(p, dopr != 0, eopt)) >= 0) 167 return x; 168 169 if (err_set <= 0 && errno != ENOENT) { 170 err = errno; 171 cp = stalloc(strlen(p) + 1); 172 err_set = 1; 173 } 174 } 175 error("Can't cd to \"%s\": %s", cp, strerror(err)); 176 /* NOTREACHED */ 177 } 178 179 180 /* 181 * Actually do the chdir. In an interactive shell, print the 182 * directory name if "print" is nonzero. 183 */ 184 185 STATIC int 186 docd(const char *dest, bool print, bool eopt) 187 { 188 bool gotpwd; 189 190 #if 0 /* no "cd -L" (ever) so all this is just a waste of time ... */ 191 char *p; 192 char *q; 193 char *component; 194 struct stat statb; 195 int first; 196 int badstat; 197 198 /* 199 * Check each component of the path. If we find a symlink or 200 * something we can't stat, clear curdir to force a getcwd() 201 * next time we get the value of the current directory. 202 */ 203 badstat = 0; 204 cdcomppath = stalloc(strlen(dest) + 1); 205 scopy(dest, cdcomppath); 206 STARTSTACKSTR(p); 207 if (*dest == '/') { 208 STPUTC('/', p); 209 cdcomppath++; 210 } 211 first = 1; 212 while ((q = getcomponent()) != NULL) { 213 if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) 214 continue; 215 if (! first) 216 STPUTC('/', p); 217 first = 0; 218 component = q; 219 while (*q) 220 STPUTC(*q++, p); 221 if (equal(component, "..")) 222 continue; 223 STACKSTRNUL(p); 224 if (lstat(stackblock(), &statb) < 0) { 225 badstat = 1; 226 break; 227 } 228 } 229 #endif 230 231 CTRACE(DBG_CMDS, ("docd(\"%s\", %s, %s) called\n", dest, 232 print ? "true" : "false", eopt ? "true" : "false")); 233 234 235 INTOFF; 236 if (chdir(dest) < 0) { 237 INTON; /* If int was pending, this does not return */ 238 return -1; 239 } 240 gotpwd = updatepwd(NULL); /* only do cd -P, no "pretend" -L mode */ 241 INTON; 242 if (!gotpwd && eopt) 243 sh_warnx("Unable to determine new working directory"); 244 else if (print && (iflag || posix)) 245 out1fmt("%s\n", curdir); 246 return gotpwd || !eopt ? 0 : 1; 247 } 248 249 250 /* 251 * Get the next component of the path name pointed to by cdcomppath. 252 * This routine overwrites the string pointed to by cdcomppath. 253 */ 254 255 STATIC char * 256 getcomponent(void) 257 { 258 char *p; 259 char *start; 260 261 if ((p = cdcomppath) == NULL) 262 return NULL; 263 start = cdcomppath; 264 while (*p != '/' && *p != '\0') 265 p++; 266 if (*p == '\0') { 267 cdcomppath = NULL; 268 } else { 269 *p++ = '\0'; 270 cdcomppath = p; 271 } 272 return start; 273 } 274 275 276 277 /* 278 * Update curdir (the name of the current directory) in response to a 279 * cd command. We also call hashcd to let the routines in exec.c know 280 * that the current directory has changed. 281 */ 282 283 STATIC bool 284 updatepwd(const char *dir) 285 { 286 char *new; 287 char *p; 288 289 hashcd(); /* update command hash table */ 290 291 /* 292 * If our argument is NULL, we don't know the current directory 293 * any more because we traversed a symbolic link or something 294 * we couldn't stat(). Or we simply don't trust what we had. 295 */ 296 if (dir == NULL || curdir == NULL) { 297 if (prevdir) 298 ckfree(prevdir); 299 INTOFF; 300 prevdir = curdir; 301 curdir = NULL; 302 getpwd(1); 303 INTON; 304 if (curdir) { 305 setvar("OLDPWD", prevdir, VEXPORT); 306 setvar("PWD", curdir, VEXPORT); 307 return true; 308 } else 309 unsetvar("PWD", 0); 310 return false; 311 } 312 313 /* XXX none of the following code is ever executed any more */ 314 315 cdcomppath = stalloc(strlen(dir) + 1); 316 scopy(dir, cdcomppath); 317 STARTSTACKSTR(new); 318 if (*dir != '/') { 319 p = curdir; 320 while (*p) 321 STPUTC(*p++, new); 322 if (p[-1] == '/') 323 STUNPUTC(new); 324 } 325 while ((p = getcomponent()) != NULL) { 326 if (equal(p, "..")) { 327 while (new > stackblock() && (STUNPUTC(new), *new) != '/'); 328 } else if (*p != '\0' && ! equal(p, ".")) { 329 STPUTC('/', new); 330 while (*p) 331 STPUTC(*p++, new); 332 } 333 } 334 if (new == stackblock()) 335 STPUTC('/', new); 336 STACKSTRNUL(new); 337 INTOFF; 338 if (prevdir) 339 ckfree(prevdir); 340 prevdir = curdir; 341 curdir = savestr(stackblock()); 342 setvar("OLDPWD", prevdir, VEXPORT); 343 setvar("PWD", curdir, VEXPORT); 344 INTON; 345 return true; 346 } 347 348 /* 349 * Test whether we are currently in the direcory given 350 * (provided it is given, and is absolute) 351 * ie: determine if path is fully qualified pathname of "." 352 */ 353 STATIC bool 354 is_curdir(const char *path) 355 { 356 struct stat stdot, stpath; 357 358 return path != NULL && 359 *path == '/' && 360 stat(".", &stdot) != -1 && 361 stat(path, &stpath) != -1 && 362 stdot.st_dev == stpath.st_dev && 363 stdot.st_ino == stpath.st_ino; 364 } 365 366 /* 367 * Posix says the default should be 'pwd -L' (as below), however 368 * the 'cd' command (above) does something much nearer to the 369 * posix 'cd -P' (not the posix default of 'cd -L'). 370 * If 'cd' is changed to support -P/L then the default here 371 * needs to be revisited if the historic behaviour is to be kept. 372 */ 373 374 int 375 pwdcmd(int argc, char **argv) 376 { 377 int i; 378 char opt = 'L'; 379 380 while ((i = nextopt("LP")) != '\0') 381 opt = i; 382 if (*argptr) 383 error("unexpected argument"); 384 385 if (opt == 'L') 386 getpwd(0); 387 else 388 find_curdir(0); 389 390 #if 0 /* posix has been changed to forbid this */ 391 setvar("OLDPWD", prevdir, VEXPORT); 392 setvar("PWD", curdir, VEXPORT); 393 #endif 394 395 if (!is_curdir(curdir)) { 396 find_curdir(1); 397 if (curdir == NULL) 398 error("Unable to find current directory"); 399 } 400 401 flushout(out1); /* make sure buffer is empty */ 402 clr_err(out1); /* and forget any earlier errors */ 403 out1str(curdir); 404 out1c('\n'); 405 flushout(out1); 406 if (io_err(out1)) 407 error("stdout: %s", strerror(errno)); 408 409 return 0; 410 } 411 412 413 414 void 415 initpwd(void) 416 { 417 getpwd(1); 418 if (curdir) 419 setvar("PWD", curdir, VEXPORT); 420 else 421 sh_warnx("Cannot determine current working directory"); 422 } 423 424 #define MAXPWD 256 425 426 /* 427 * Find out what the current directory is. If we already know the current 428 * directory, this routine returns immediately. 429 */ 430 void 431 getpwd(int noerror) 432 { 433 char *pwd; 434 static int first = 1; 435 436 if (curdir) 437 return; 438 439 if (first) { 440 /* 441 * Note that this happens via the call from initpwd() 442 * just above, which is called early from main() during 443 * sh startup, so fetching PWD from the entry environment 444 * (which is what getenv() does) is acceptable. Here we 445 * could use normal sh var lookup functions instead, as 446 * the arriving environment has already been imported before 447 * we get here, but it makes little difference. 448 * 449 * XXX What would be better perhaps would be to move all of 450 * this into initpwd() instead of here, so we could get rid of 451 * this "first" static - that function is only ever called once. 452 * XXX Some other day. 453 */ 454 first = 0; 455 pwd = getenv("PWD"); 456 if (is_curdir(pwd)) { 457 curdir = savestr(pwd); 458 return; 459 } 460 } 461 462 find_curdir(noerror); 463 464 return; 465 } 466 467 STATIC void 468 find_curdir(int noerror) 469 { 470 int i; 471 char *pwd; 472 473 /* 474 * Things are a bit complicated here; we could have just used 475 * getcwd, but traditionally getcwd is implemented using popen 476 * to /bin/pwd. This creates a problem for us, since we cannot 477 * keep track of the job if it is being ran behind our backs. 478 * XXX That's not actually the problem, a process created and 479 * XXX destroyed that we know nothing about is harmless. The 480 * XXX problem is that old popen() implementations would use 481 * XXX wait(2) to await completion of the command, and that might 482 * XXX collect (and ignore) our children. As long as we are 483 * XXX confident that popen() uses waitpid() (or the equv) there 484 * XXX would not be a problem. But how do we know that? 485 * So we re-implement getcwd(), and we suppress interrupts 486 * throughout the process. This is not completely safe, since 487 * the user can still break out of it by killing the pwd program. 488 * We still try to use getcwd for systems that we know have a 489 * c implementation of getcwd, that does not open a pipe to 490 * /bin/pwd. 491 */ 492 #if defined(__NetBSD__) || defined(__SVR4) 493 494 for (i = MAXPWD;; i *= 2) { 495 pwd = stalloc(i); 496 if (getcwd(pwd, i) != NULL) { 497 curdir = savestr(pwd); 498 stunalloc(pwd); 499 return; 500 } 501 stunalloc(pwd); 502 if (errno == ERANGE) 503 continue; 504 if (!noerror) 505 error("getcwd() failed: %s", strerror(errno)); 506 return; 507 } 508 #else 509 { 510 char *p; 511 int status; 512 struct job *jp; 513 int pip[2]; 514 515 pwd = stalloc(MAXPWD); 516 INTOFF; 517 if (pipe(pip) < 0) 518 error("Pipe call failed"); 519 jp = makejob(NULL, 1); 520 if (forkshell(jp, NULL, FORK_NOJOB) == 0) { 521 (void) close(pip[0]); 522 movefd(pip[1], 1); 523 (void) execl("/bin/pwd", "pwd", (char *)0); 524 error("Cannot exec /bin/pwd"); 525 } 526 (void) close(pip[1]); 527 pip[1] = -1; 528 p = pwd; 529 while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0 530 || (i == -1 && errno == EINTR)) { 531 if (i > 0) 532 p += i; 533 } 534 (void) close(pip[0]); 535 pip[0] = -1; 536 status = waitforjob(jp); 537 if (status != 0) 538 error((char *)0); 539 if (i < 0 || p == pwd || p[-1] != '\n') { 540 if (noerror) { 541 INTON; 542 return; 543 } 544 error("pwd command failed"); 545 } 546 p[-1] = '\0'; 547 INTON; 548 curdir = savestr(pwd); 549 stunalloc(pwd); 550 return; 551 } 552 #endif 553 } 554