1 /* $NetBSD: cd.c,v 1.50 2017/07/05 20:00:27 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.50 2017/07/05 20:00:27 kre Exp $"); 41 #endif 42 #endif /* not lint */ 43 44 #include <sys/types.h> 45 #include <sys/stat.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <errno.h> 50 51 /* 52 * The cd and pwd commands. 53 */ 54 55 #include "shell.h" 56 #include "var.h" 57 #include "nodes.h" /* for jobs.h */ 58 #include "jobs.h" 59 #include "options.h" 60 #include "builtins.h" 61 #include "output.h" 62 #include "memalloc.h" 63 #include "error.h" 64 #include "exec.h" 65 #include "redir.h" 66 #include "mystring.h" 67 #include "show.h" 68 #include "cd.h" 69 70 STATIC int docd(const char *, int); 71 STATIC char *getcomponent(void); 72 STATIC void updatepwd(const char *); 73 STATIC void find_curdir(int noerror); 74 75 char *curdir = NULL; /* current working directory */ 76 char *prevdir; /* previous working directory */ 77 STATIC char *cdcomppath; 78 79 int 80 cdcmd(int argc, char **argv) 81 { 82 const char *dest; 83 const char *path, *cp; 84 char *p; 85 char *d; 86 struct stat statb; 87 int print = cdprint; /* set -o cdprint to enable */ 88 89 while (nextopt("P") != '\0') 90 ; 91 92 /* 93 * Try (quite hard) to have 'curdir' defined, nothing has set 94 * it on entry to the shell, but we want 'cd fred; cd -' to work. 95 */ 96 getpwd(1); 97 dest = *argptr; 98 if (dest == NULL) { 99 dest = bltinlookup("HOME", 1); 100 if (dest == NULL) 101 error("HOME not set"); 102 } else if (argptr[1]) { 103 /* Do 'ksh' style substitution */ 104 if (!curdir) 105 error("PWD not set"); 106 p = strstr(curdir, dest); 107 if (!p) 108 error("bad substitution"); 109 d = stalloc(strlen(curdir) + strlen(argptr[1]) + 1); 110 memcpy(d, curdir, p - curdir); 111 strcpy(d + (p - curdir), argptr[1]); 112 strcat(d, p + strlen(dest)); 113 dest = d; 114 print = 1; 115 } else if (dest[0] == '-' && dest[1] == '\0') { 116 dest = prevdir ? prevdir : curdir; 117 print = 1; 118 } 119 if (*dest == '\0') 120 dest = "."; 121 122 cp = dest; 123 if (*cp == '.' && *++cp == '.') 124 cp++; 125 if (*cp == 0 || *cp == '/' || (path = bltinlookup("CDPATH", 1)) == NULL) 126 path = nullstr; 127 while ((p = padvance(&path, dest, 0)) != NULL) { 128 stunalloc(p); 129 if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { 130 int dopr = print; 131 132 if (!print) { 133 /* 134 * XXX - rethink 135 */ 136 if (p[0] == '.' && p[1] == '/' && p[2] != '\0') 137 dopr = strcmp(p + 2, dest); 138 else 139 dopr = strcmp(p, dest); 140 } 141 if (docd(p, dopr) >= 0) 142 return 0; 143 144 } 145 } 146 error("can't cd to %s", dest); 147 /* NOTREACHED */ 148 } 149 150 151 /* 152 * Actually do the chdir. In an interactive shell, print the 153 * directory name if "print" is nonzero. 154 */ 155 156 STATIC int 157 docd(const char *dest, int print) 158 { 159 #if 0 /* no "cd -L" (ever) so all this is just a waste of time ... */ 160 char *p; 161 char *q; 162 char *component; 163 struct stat statb; 164 int first; 165 int badstat; 166 167 CTRACE(DBG_CMDS, ("docd(\"%s\", %d) called\n", dest, print)); 168 169 /* 170 * Check each component of the path. If we find a symlink or 171 * something we can't stat, clear curdir to force a getcwd() 172 * next time we get the value of the current directory. 173 */ 174 badstat = 0; 175 cdcomppath = stalloc(strlen(dest) + 1); 176 scopy(dest, cdcomppath); 177 STARTSTACKSTR(p); 178 if (*dest == '/') { 179 STPUTC('/', p); 180 cdcomppath++; 181 } 182 first = 1; 183 while ((q = getcomponent()) != NULL) { 184 if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) 185 continue; 186 if (! first) 187 STPUTC('/', p); 188 first = 0; 189 component = q; 190 while (*q) 191 STPUTC(*q++, p); 192 if (equal(component, "..")) 193 continue; 194 STACKSTRNUL(p); 195 if (lstat(stackblock(), &statb) < 0) { 196 badstat = 1; 197 break; 198 } 199 } 200 #endif 201 202 INTOFF; 203 if (chdir(dest) < 0) { 204 INTON; 205 return -1; 206 } 207 updatepwd(NULL); /* only do cd -P, no "pretend" -L mode */ 208 INTON; 209 if (print && iflag == 1 && curdir) 210 out1fmt("%s\n", curdir); 211 return 0; 212 } 213 214 215 /* 216 * Get the next component of the path name pointed to by cdcomppath. 217 * This routine overwrites the string pointed to by cdcomppath. 218 */ 219 220 STATIC char * 221 getcomponent(void) 222 { 223 char *p; 224 char *start; 225 226 if ((p = cdcomppath) == NULL) 227 return NULL; 228 start = cdcomppath; 229 while (*p != '/' && *p != '\0') 230 p++; 231 if (*p == '\0') { 232 cdcomppath = NULL; 233 } else { 234 *p++ = '\0'; 235 cdcomppath = p; 236 } 237 return start; 238 } 239 240 241 242 /* 243 * Update curdir (the name of the current directory) in response to a 244 * cd command. We also call hashcd to let the routines in exec.c know 245 * that the current directory has changed. 246 */ 247 248 STATIC void 249 updatepwd(const char *dir) 250 { 251 char *new; 252 char *p; 253 254 hashcd(); /* update command hash table */ 255 256 /* 257 * If our argument is NULL, we don't know the current directory 258 * any more because we traversed a symbolic link or something 259 * we couldn't stat(). 260 */ 261 if (dir == NULL || curdir == NULL) { 262 if (prevdir) 263 ckfree(prevdir); 264 INTOFF; 265 prevdir = curdir; 266 curdir = NULL; 267 getpwd(1); 268 INTON; 269 if (curdir) { 270 setvar("OLDPWD", prevdir, VEXPORT); 271 setvar("PWD", curdir, VEXPORT); 272 } else 273 unsetvar("PWD", 0); 274 return; 275 } 276 cdcomppath = stalloc(strlen(dir) + 1); 277 scopy(dir, cdcomppath); 278 STARTSTACKSTR(new); 279 if (*dir != '/') { 280 p = curdir; 281 while (*p) 282 STPUTC(*p++, new); 283 if (p[-1] == '/') 284 STUNPUTC(new); 285 } 286 while ((p = getcomponent()) != NULL) { 287 if (equal(p, "..")) { 288 while (new > stackblock() && (STUNPUTC(new), *new) != '/'); 289 } else if (*p != '\0' && ! equal(p, ".")) { 290 STPUTC('/', new); 291 while (*p) 292 STPUTC(*p++, new); 293 } 294 } 295 if (new == stackblock()) 296 STPUTC('/', new); 297 STACKSTRNUL(new); 298 INTOFF; 299 if (prevdir) 300 ckfree(prevdir); 301 prevdir = curdir; 302 curdir = savestr(stackblock()); 303 setvar("OLDPWD", prevdir, VEXPORT); 304 setvar("PWD", curdir, VEXPORT); 305 INTON; 306 } 307 308 /* 309 * Posix says the default should be 'pwd -L' (as below), however 310 * the 'cd' command (above) does something much nearer to the 311 * posix 'cd -P' (not the posix default of 'cd -L'). 312 * If 'cd' is changed to support -P/L then the default here 313 * needs to be revisited if the historic behaviour is to be kept. 314 */ 315 316 int 317 pwdcmd(int argc, char **argv) 318 { 319 int i; 320 char opt = 'L'; 321 322 while ((i = nextopt("LP")) != '\0') 323 opt = i; 324 if (*argptr) 325 error("unexpected argument"); 326 327 if (opt == 'L') 328 getpwd(0); 329 else 330 find_curdir(0); 331 332 setvar("OLDPWD", prevdir, VEXPORT); 333 setvar("PWD", curdir, VEXPORT); 334 out1str(curdir); 335 out1c('\n'); 336 return 0; 337 } 338 339 340 341 void 342 initpwd(void) 343 { 344 getpwd(1); 345 if (curdir) 346 setvar("PWD", curdir, VEXPORT); 347 else 348 sh_warnx("Cannot determine current working directory"); 349 } 350 351 #define MAXPWD 256 352 353 /* 354 * Find out what the current directory is. If we already know the current 355 * directory, this routine returns immediately. 356 */ 357 void 358 getpwd(int noerror) 359 { 360 char *pwd; 361 struct stat stdot, stpwd; 362 static int first = 1; 363 364 if (curdir) 365 return; 366 367 if (first) { 368 first = 0; 369 pwd = getenv("PWD"); 370 if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && 371 stat(pwd, &stpwd) != -1 && 372 stdot.st_dev == stpwd.st_dev && 373 stdot.st_ino == stpwd.st_ino) { 374 curdir = savestr(pwd); 375 return; 376 } 377 } 378 379 find_curdir(noerror); 380 381 return; 382 } 383 384 STATIC void 385 find_curdir(int noerror) 386 { 387 int i; 388 char *pwd; 389 390 /* 391 * Things are a bit complicated here; we could have just used 392 * getcwd, but traditionally getcwd is implemented using popen 393 * to /bin/pwd. This creates a problem for us, since we cannot 394 * keep track of the job if it is being ran behind our backs. 395 * So we re-implement getcwd(), and we suppress interrupts 396 * throughout the process. This is not completely safe, since 397 * the user can still break out of it by killing the pwd program. 398 * We still try to use getcwd for systems that we know have a 399 * c implementation of getcwd, that does not open a pipe to 400 * /bin/pwd. 401 */ 402 #if defined(__NetBSD__) || defined(__SVR4) 403 404 for (i = MAXPWD;; i *= 2) { 405 pwd = stalloc(i); 406 if (getcwd(pwd, i) != NULL) { 407 curdir = savestr(pwd); 408 stunalloc(pwd); 409 return; 410 } 411 stunalloc(pwd); 412 if (errno == ERANGE) 413 continue; 414 if (!noerror) 415 error("getcwd() failed: %s", strerror(errno)); 416 return; 417 } 418 #else 419 { 420 char *p; 421 int status; 422 struct job *jp; 423 int pip[2]; 424 425 pwd = stalloc(MAXPWD); 426 INTOFF; 427 if (pipe(pip) < 0) 428 error("Pipe call failed"); 429 jp = makejob(NULL, 1); 430 if (forkshell(jp, NULL, FORK_NOJOB) == 0) { 431 (void) close(pip[0]); 432 movefd(pip[1], 1); 433 (void) execl("/bin/pwd", "pwd", (char *)0); 434 error("Cannot exec /bin/pwd"); 435 } 436 (void) close(pip[1]); 437 pip[1] = -1; 438 p = pwd; 439 while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0 440 || (i == -1 && errno == EINTR)) { 441 if (i > 0) 442 p += i; 443 } 444 (void) close(pip[0]); 445 pip[0] = -1; 446 status = waitforjob(jp); 447 if (status != 0) 448 error((char *)0); 449 if (i < 0 || p == pwd || p[-1] != '\n') { 450 if (noerror) { 451 INTON; 452 return; 453 } 454 error("pwd command failed"); 455 } 456 p[-1] = '\0'; 457 INTON; 458 curdir = savestr(pwd); 459 stunalloc(pwd); 460 return; 461 } 462 #endif 463 } 464