1 /* $NetBSD: cd.c,v 1.53 2022/01/31 16:54:28 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.53 2022/01/31 16:54:28 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 char opt; 90 bool eopt = false; 91 int print = cdprint; /* set -o cdprint to enable */ 92 93 while ((opt = nextopt("Pe")) != '\0') 94 if (opt == 'e') 95 eopt = true; 96 97 /* 98 * Try (quite hard) to have 'curdir' defined, nothing has set 99 * it on entry to the shell, but we want 'cd fred; cd -' to work. 100 */ 101 getpwd(1); 102 dest = *argptr; 103 if (dest == NULL) { 104 dest = bltinlookup("HOME", 1); 105 if (dest == NULL) 106 error("HOME not set"); 107 } else if (argptr[1]) { 108 /* Do 'ksh' style substitution */ 109 if (!curdir) 110 error("PWD not set"); 111 p = strstr(curdir, dest); 112 if (!p) 113 error("bad substitution"); 114 d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1); 115 memcpy(d, curdir, p - curdir); 116 strcpy(d + (p - curdir), argptr[1]); 117 strcat(d, p + strlen(dest)); 118 dest = d; 119 print = 1; 120 } else if (dest[0] == '-' && dest[1] == '\0') { 121 dest = prevdir ? prevdir : curdir; 122 print = 1; 123 } 124 if (*dest == '\0') 125 dest = "."; 126 127 cp = dest; 128 if (*cp == '.' && *++cp == '.') 129 cp++; 130 if (*cp == 0 || *cp == '/' || (path = bltinlookup("CDPATH", 1)) == NULL) 131 path = nullstr; 132 while ((p = padvance(&path, dest, 0)) != NULL) { 133 stunalloc(p); 134 if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { 135 int dopr = print; 136 int x; 137 138 if (!print) 139 dopr = strcmp(p, dest); 140 141 if ((x = docd(p, dopr != 0, eopt)) >= 0) 142 return x; 143 } 144 } 145 error("can't cd to %s", dest); 146 /* NOTREACHED */ 147 } 148 149 150 /* 151 * Actually do the chdir. In an interactive shell, print the 152 * directory name if "print" is nonzero. 153 */ 154 155 STATIC int 156 docd(const char *dest, bool print, bool eopt) 157 { 158 bool gotpwd; 159 160 #if 0 /* no "cd -L" (ever) so all this is just a waste of time ... */ 161 char *p; 162 char *q; 163 char *component; 164 struct stat statb; 165 int first; 166 int badstat; 167 168 /* 169 * Check each component of the path. If we find a symlink or 170 * something we can't stat, clear curdir to force a getcwd() 171 * next time we get the value of the current directory. 172 */ 173 badstat = 0; 174 cdcomppath = stalloc(strlen(dest) + 1); 175 scopy(dest, cdcomppath); 176 STARTSTACKSTR(p); 177 if (*dest == '/') { 178 STPUTC('/', p); 179 cdcomppath++; 180 } 181 first = 1; 182 while ((q = getcomponent()) != NULL) { 183 if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) 184 continue; 185 if (! first) 186 STPUTC('/', p); 187 first = 0; 188 component = q; 189 while (*q) 190 STPUTC(*q++, p); 191 if (equal(component, "..")) 192 continue; 193 STACKSTRNUL(p); 194 if (lstat(stackblock(), &statb) < 0) { 195 badstat = 1; 196 break; 197 } 198 } 199 #endif 200 201 CTRACE(DBG_CMDS, ("docd(\"%s\", %s, %s) called\n", dest, 202 print ? "true" : "false", eopt ? "true" : "false")); 203 204 INTOFF; 205 if (chdir(dest) < 0) { 206 INTON; 207 return -1; 208 } 209 gotpwd = updatepwd(NULL); /* only do cd -P, no "pretend" -L mode */ 210 INTON; 211 if (print && (iflag || posix)) 212 out1fmt("%s\n", gotpwd ? curdir : dest); 213 return gotpwd || !eopt ? 0 : 1; 214 } 215 216 217 /* 218 * Get the next component of the path name pointed to by cdcomppath. 219 * This routine overwrites the string pointed to by cdcomppath. 220 */ 221 222 STATIC char * 223 getcomponent(void) 224 { 225 char *p; 226 char *start; 227 228 if ((p = cdcomppath) == NULL) 229 return NULL; 230 start = cdcomppath; 231 while (*p != '/' && *p != '\0') 232 p++; 233 if (*p == '\0') { 234 cdcomppath = NULL; 235 } else { 236 *p++ = '\0'; 237 cdcomppath = p; 238 } 239 return start; 240 } 241 242 243 244 /* 245 * Update curdir (the name of the current directory) in response to a 246 * cd command. We also call hashcd to let the routines in exec.c know 247 * that the current directory has changed. 248 */ 249 250 STATIC bool 251 updatepwd(const char *dir) 252 { 253 char *new; 254 char *p; 255 256 hashcd(); /* update command hash table */ 257 258 /* 259 * If our argument is NULL, we don't know the current directory 260 * any more because we traversed a symbolic link or something 261 * we couldn't stat(). Or we simply don't trust what we had. 262 */ 263 if (dir == NULL || curdir == NULL) { 264 if (prevdir) 265 ckfree(prevdir); 266 INTOFF; 267 prevdir = curdir; 268 curdir = NULL; 269 getpwd(1); 270 INTON; 271 if (curdir) { 272 setvar("OLDPWD", prevdir, VEXPORT); 273 setvar("PWD", curdir, VEXPORT); 274 return true; 275 } else 276 unsetvar("PWD", 0); 277 return false; 278 } 279 280 /* XXX none of the following code is ever executed any more */ 281 282 cdcomppath = stalloc(strlen(dir) + 1); 283 scopy(dir, cdcomppath); 284 STARTSTACKSTR(new); 285 if (*dir != '/') { 286 p = curdir; 287 while (*p) 288 STPUTC(*p++, new); 289 if (p[-1] == '/') 290 STUNPUTC(new); 291 } 292 while ((p = getcomponent()) != NULL) { 293 if (equal(p, "..")) { 294 while (new > stackblock() && (STUNPUTC(new), *new) != '/'); 295 } else if (*p != '\0' && ! equal(p, ".")) { 296 STPUTC('/', new); 297 while (*p) 298 STPUTC(*p++, new); 299 } 300 } 301 if (new == stackblock()) 302 STPUTC('/', new); 303 STACKSTRNUL(new); 304 INTOFF; 305 if (prevdir) 306 ckfree(prevdir); 307 prevdir = curdir; 308 curdir = savestr(stackblock()); 309 setvar("OLDPWD", prevdir, VEXPORT); 310 setvar("PWD", curdir, VEXPORT); 311 INTON; 312 return true; 313 } 314 315 /* 316 * Test whether we are currently in the direcory given 317 * (provided it is given, and is absolute) 318 * ie: determine if path is fully qualified pathname of "." 319 */ 320 STATIC bool 321 is_curdir(const char *path) 322 { 323 struct stat stdot, stpath; 324 325 return path != NULL && 326 *path == '/' && 327 stat(".", &stdot) != -1 && 328 stat(path, &stpath) != -1 && 329 stdot.st_dev == stpath.st_dev && 330 stdot.st_ino == stpath.st_ino; 331 } 332 333 /* 334 * Posix says the default should be 'pwd -L' (as below), however 335 * the 'cd' command (above) does something much nearer to the 336 * posix 'cd -P' (not the posix default of 'cd -L'). 337 * If 'cd' is changed to support -P/L then the default here 338 * needs to be revisited if the historic behaviour is to be kept. 339 */ 340 341 int 342 pwdcmd(int argc, char **argv) 343 { 344 int i; 345 char opt = 'L'; 346 347 while ((i = nextopt("LP")) != '\0') 348 opt = i; 349 if (*argptr) 350 error("unexpected argument"); 351 352 if (opt == 'L') 353 getpwd(0); 354 else 355 find_curdir(0); 356 357 #if 0 /* posix has been changed to forbid this */ 358 setvar("OLDPWD", prevdir, VEXPORT); 359 setvar("PWD", curdir, VEXPORT); 360 #endif 361 362 if (!is_curdir(curdir)) { 363 find_curdir(1); 364 if (curdir == NULL) 365 error("Unable to find current directory"); 366 } 367 368 flushout(out1); /* make sure buffer is empty */ 369 clr_err(out1); /* and forget any earlier errors */ 370 out1str(curdir); 371 out1c('\n'); 372 flushout(out1); 373 if (io_err(out1)) 374 error("stdout: %s", strerror(errno)); 375 376 return 0; 377 } 378 379 380 381 void 382 initpwd(void) 383 { 384 getpwd(1); 385 if (curdir) 386 setvar("PWD", curdir, VEXPORT); 387 else 388 sh_warnx("Cannot determine current working directory"); 389 } 390 391 #define MAXPWD 256 392 393 /* 394 * Find out what the current directory is. If we already know the current 395 * directory, this routine returns immediately. 396 */ 397 void 398 getpwd(int noerror) 399 { 400 char *pwd; 401 static int first = 1; 402 403 if (curdir) 404 return; 405 406 if (first) { 407 /* 408 * Note that this happens via the call from initpwd() 409 * just above, which is called early from main() during 410 * sh startup, so fetching PWD from the entry environment 411 * (which is what getenv() does) is acceptable. Here we 412 * could use normal sh var lookup functions instead, as 413 * the arriving environment has already been imported before 414 * we get here, but it makes little difference. 415 * 416 * XXX What would be better perhaps would be to move all of 417 * this into initpwd() instead of here, so we could get rid of 418 * this "first" static - that function is only ever called once. 419 * XXX Some other day. 420 */ 421 first = 0; 422 pwd = getenv("PWD"); 423 if (is_curdir(pwd)) { 424 curdir = savestr(pwd); 425 return; 426 } 427 } 428 429 find_curdir(noerror); 430 431 return; 432 } 433 434 STATIC void 435 find_curdir(int noerror) 436 { 437 int i; 438 char *pwd; 439 440 /* 441 * Things are a bit complicated here; we could have just used 442 * getcwd, but traditionally getcwd is implemented using popen 443 * to /bin/pwd. This creates a problem for us, since we cannot 444 * keep track of the job if it is being ran behind our backs. 445 * XXX That's not actually the problem, a process created and 446 * XXX destroyed that we know nothing about is harmless. The 447 * XXX problem is that old popen() implementations would use 448 * XXX wait(2) to await completion of the command, and that might 449 * XXX collect (and ignore) our children. As long as we are 450 * XXX confident that popen() uses waitpid() (or the equv) there 451 * XXX would not be a problem. But how do we know that? 452 * So we re-implement getcwd(), and we suppress interrupts 453 * throughout the process. This is not completely safe, since 454 * the user can still break out of it by killing the pwd program. 455 * We still try to use getcwd for systems that we know have a 456 * c implementation of getcwd, that does not open a pipe to 457 * /bin/pwd. 458 */ 459 #if defined(__NetBSD__) || defined(__SVR4) 460 461 for (i = MAXPWD;; i *= 2) { 462 pwd = stalloc(i); 463 if (getcwd(pwd, i) != NULL) { 464 curdir = savestr(pwd); 465 stunalloc(pwd); 466 return; 467 } 468 stunalloc(pwd); 469 if (errno == ERANGE) 470 continue; 471 if (!noerror) 472 error("getcwd() failed: %s", strerror(errno)); 473 return; 474 } 475 #else 476 { 477 char *p; 478 int status; 479 struct job *jp; 480 int pip[2]; 481 482 pwd = stalloc(MAXPWD); 483 INTOFF; 484 if (pipe(pip) < 0) 485 error("Pipe call failed"); 486 jp = makejob(NULL, 1); 487 if (forkshell(jp, NULL, FORK_NOJOB) == 0) { 488 (void) close(pip[0]); 489 movefd(pip[1], 1); 490 (void) execl("/bin/pwd", "pwd", (char *)0); 491 error("Cannot exec /bin/pwd"); 492 } 493 (void) close(pip[1]); 494 pip[1] = -1; 495 p = pwd; 496 while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0 497 || (i == -1 && errno == EINTR)) { 498 if (i > 0) 499 p += i; 500 } 501 (void) close(pip[0]); 502 pip[0] = -1; 503 status = waitforjob(jp); 504 if (status != 0) 505 error((char *)0); 506 if (i < 0 || p == pwd || p[-1] != '\n') { 507 if (noerror) { 508 INTON; 509 return; 510 } 511 error("pwd command failed"); 512 } 513 p[-1] = '\0'; 514 INTON; 515 curdir = savestr(pwd); 516 stunalloc(pwd); 517 return; 518 } 519 #endif 520 } 521