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