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